From 6653ccc011dec86e5140a5d09ea3b2357eab6714 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Fri, 12 Mar 2021 16:26:10 +0000 Subject: Add latest changes from gitlab-org/gitlab@13-10-stable-ee --- spec/graphql/features/authorization_spec.rb | 37 +++-- spec/graphql/features/feature_flag_spec.rb | 3 +- spec/graphql/gitlab_schema_spec.rb | 25 ++- spec/graphql/mutations/boards/update_spec.rb | 57 +++++++ .../mutations/can_mutate_spammable_spec.rb | 2 +- spec/graphql/mutations/custom_emoji/create_spec.rb | 27 ++++ .../mutations/merge_requests/accept_spec.rb | 171 +++++++++++++++++++++ .../mutations/release_asset_links/create_spec.rb | 105 +++++++++++++ .../graphql/mutations/user_callouts/create_spec.rb | 42 +++++ .../measurements_resolver_spec.rb | 100 ------------ .../usage_trends/measurements_resolver_spec.rb | 100 ++++++++++++ .../http_integrations_resolver_spec.rb | 51 ++++++ spec/graphql/resolvers/board_resolver_spec.rb | 4 +- spec/graphql/resolvers/boards_resolver_spec.rb | 4 +- .../resolvers/branch_commit_resolver_spec.rb | 4 + .../concerns/caching_array_resolver_spec.rb | 3 +- .../error_tracking/sentry_errors_resolver_spec.rb | 6 +- .../resolvers/group_labels_resolver_spec.rb | 2 +- .../resolvers/group_packages_resolver_spec.rb | 18 +++ spec/graphql/resolvers/issues_resolver_spec.rb | 8 +- spec/graphql/resolvers/labels_resolver_spec.rb | 36 ++--- .../resolvers/merge_requests_resolver_spec.rb | 2 +- .../resolvers/namespace_projects_resolver_spec.rb | 57 ++++++- spec/graphql/resolvers/packages_resolver_spec.rb | 17 -- .../resolvers/project_packages_resolver_spec.rb | 17 ++ .../resolvers/project_pipeline_resolver_spec.rb | 37 ++++- .../resolvers/release_milestones_resolver_spec.rb | 2 +- spec/graphql/types/access_level_enum_spec.rb | 2 +- .../measurement_identifier_enum_spec.rb | 16 -- .../instance_statistics/measurement_type_spec.rb | 55 ------- .../measurement_identifier_enum_spec.rb | 16 ++ .../usage_trends/measurement_type_spec.rb | 55 +++++++ .../types/alert_management/alert_type_spec.rb | 3 +- spec/graphql/types/base_argument_spec.rb | 17 ++ spec/graphql/types/board_type_spec.rb | 2 +- spec/graphql/types/ci/job_type_spec.rb | 2 + spec/graphql/types/ci/pipeline_type_spec.rb | 4 +- spec/graphql/types/global_id_type_spec.rb | 23 ++- spec/graphql/types/group_type_spec.rb | 1 + spec/graphql/types/label_type_spec.rb | 11 +- spec/graphql/types/merge_request_type_spec.rb | 31 +++- spec/graphql/types/query_type_spec.rb | 10 +- spec/graphql/types/snippet_type_spec.rb | 10 ++ spec/graphql/types/snippets/blob_type_spec.rb | 47 ++++-- .../types/user_callout_feature_name_enum_spec.rb | 11 ++ spec/graphql/types/user_callout_type_spec.rb | 11 ++ spec/graphql/types/user_type_spec.rb | 9 ++ 47 files changed, 969 insertions(+), 304 deletions(-) create mode 100644 spec/graphql/mutations/boards/update_spec.rb create mode 100644 spec/graphql/mutations/custom_emoji/create_spec.rb create mode 100644 spec/graphql/mutations/merge_requests/accept_spec.rb create mode 100644 spec/graphql/mutations/release_asset_links/create_spec.rb create mode 100644 spec/graphql/mutations/user_callouts/create_spec.rb delete mode 100644 spec/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver_spec.rb create mode 100644 spec/graphql/resolvers/admin/analytics/usage_trends/measurements_resolver_spec.rb create mode 100644 spec/graphql/resolvers/alert_management/http_integrations_resolver_spec.rb create mode 100644 spec/graphql/resolvers/group_packages_resolver_spec.rb delete mode 100644 spec/graphql/resolvers/packages_resolver_spec.rb create mode 100644 spec/graphql/resolvers/project_packages_resolver_spec.rb delete mode 100644 spec/graphql/types/admin/analytics/instance_statistics/measurement_identifier_enum_spec.rb delete mode 100644 spec/graphql/types/admin/analytics/instance_statistics/measurement_type_spec.rb create mode 100644 spec/graphql/types/admin/analytics/usage_trends/measurement_identifier_enum_spec.rb create mode 100644 spec/graphql/types/admin/analytics/usage_trends/measurement_type_spec.rb create mode 100644 spec/graphql/types/base_argument_spec.rb create mode 100644 spec/graphql/types/user_callout_feature_name_enum_spec.rb create mode 100644 spec/graphql/types/user_callout_type_spec.rb (limited to 'spec/graphql') 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/instance_statistics/measurements_resolver_spec.rb deleted file mode 100644 index 578d679ade4..00000000000 --- a/spec/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver_spec.rb +++ /dev/null @@ -1,100 +0,0 @@ -# frozen_string_literal: true - -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(: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(:arguments) { { identifier: 'projects' } } - - subject { resolve_measurements(arguments, { current_user: current_user }) } - - context 'when requesting project count measurements' do - context 'as an admin user' do - let(:current_user) { admin_user } - - it 'returns the records, latest first' do - expect(subject).to eq([project_measurement_new, project_measurement_old]) - end - end - - context 'as a non-admin user' do - let(:current_user) { user } - - it 'raises ResourceNotAvailable error' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) - end - end - - context 'as an unauthenticated user' do - let(:current_user) { nil } - - it 'raises ResourceNotAvailable error' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) - end - end - - context 'when filtering by recorded_after and recorded_before' do - before do - arguments[:recorded_after] = 4.days.ago - arguments[:recorded_before] = 1.day.ago - end - - it { is_expected.to match_array([project_measurement_new]) } - - context 'when "incorrect" values are passed' do - before do - arguments[:recorded_after] = 1.day.ago - arguments[:recorded_before] = 4.days.ago - end - - it { is_expected.to be_empty } - 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 = {}) - resolve(described_class, args: args, ctx: context) - end -end diff --git a/spec/graphql/resolvers/admin/analytics/usage_trends/measurements_resolver_spec.rb b/spec/graphql/resolvers/admin/analytics/usage_trends/measurements_resolver_spec.rb new file mode 100644 index 00000000000..269a1fb1758 --- /dev/null +++ b/spec/graphql/resolvers/admin/analytics/usage_trends/measurements_resolver_spec.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::Admin::Analytics::UsageTrends::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(: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' } } + + subject { resolve_measurements(arguments, { current_user: current_user }) } + + context 'when requesting project count measurements' do + context 'as an admin user' do + let(:current_user) { admin_user } + + it 'returns the records, latest first' do + expect(subject).to eq([project_measurement_new, project_measurement_old]) + end + end + + context 'as a non-admin user' do + let(:current_user) { user } + + it 'raises ResourceNotAvailable error' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + context 'as an unauthenticated user' do + let(:current_user) { nil } + + it 'raises ResourceNotAvailable error' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + context 'when filtering by recorded_after and recorded_before' do + before do + arguments[:recorded_after] = 4.days.ago + arguments[:recorded_before] = 1.day.ago + end + + it { is_expected.to match_array([project_measurement_new]) } + + context 'when "incorrect" values are passed' do + before do + arguments[:recorded_after] = 1.day.ago + arguments[:recorded_before] = 4.days.ago + end + + it { is_expected.to be_empty } + end + end + end + + context 'when requesting pipeline counts by pipeline status' do + 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 }) } + + 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 = {}) + resolve(described_class, args: args, ctx: context) + end +end 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/packages_resolver_spec.rb deleted file mode 100644 index bc0588daf7f..00000000000 --- a/spec/graphql/resolvers/packages_resolver_spec.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Resolvers::PackagesResolver do - include GraphqlHelpers - - let_it_be(:user) { create(:user) } - let_it_be(:project) { create(:project, :public) } - let_it_be(:package) { create(:package, project: project) } - - describe '#resolve' do - subject(:packages) { resolve(described_class, ctx: { current_user: user }, obj: project) } - - it { is_expected.to contain_exactly(package) } - end -end diff --git a/spec/graphql/resolvers/project_packages_resolver_spec.rb b/spec/graphql/resolvers/project_packages_resolver_spec.rb new file mode 100644 index 00000000000..c8105ed2a38 --- /dev/null +++ b/spec/graphql/resolvers/project_packages_resolver_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::ProjectPackagesResolver do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :public) } + let_it_be(:package) { create(:package, project: project) } + + describe '#resolve' do + subject(:packages) { resolve(described_class, ctx: { current_user: user }, obj: project) } + + it { is_expected.to contain_exactly(package) } + end +end 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/instance_statistics/measurement_identifier_enum_spec.rb deleted file mode 100644 index 8a7408224a2..00000000000 --- a/spec/graphql/types/admin/analytics/instance_statistics/measurement_identifier_enum_spec.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe GitlabSchema.types['MeasurementIdentifier'] do - specify { expect(described_class.graphql_name).to eq('MeasurementIdentifier') } - - it 'exposes all the existing identifier values' do - ee_only_identifiers = %w[billable_users] - identifiers = Analytics::InstanceStatistics::Measurement.identifiers.keys.reject do |x| - ee_only_identifiers.include?(x) - end.map(&:upcase) - - expect(described_class.values.keys).to match_array(identifiers) - end -end diff --git a/spec/graphql/types/admin/analytics/instance_statistics/measurement_type_spec.rb b/spec/graphql/types/admin/analytics/instance_statistics/measurement_type_spec.rb deleted file mode 100644 index ffb1a0f30c9..00000000000 --- a/spec/graphql/types/admin/analytics/instance_statistics/measurement_type_spec.rb +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe GitlabSchema.types['InstanceStatisticsMeasurement'] do - subject { described_class } - - it { is_expected.to have_graphql_field(:recorded_at) } - it { is_expected.to have_graphql_field(:identifier) } - it { is_expected.to have_graphql_field(:count) } - - describe 'authorization' do - let_it_be(:measurement) { create(:instance_statistics_measurement, :project_count) } - let(:user) { create(:user) } - - let(:query) do - <<~GRAPHQL - query instanceStatisticsMeasurements($identifier: MeasurementIdentifier!) { - instanceStatisticsMeasurements(identifier: $identifier) { - nodes { - count - identifier - } - } - } - GRAPHQL - end - - subject do - GitlabSchema.execute( - query, - variables: { identifier: 'PROJECTS' }, - context: { current_user: user } - ).to_h - end - - context 'when the user is not admin' do - it 'returns no data' do - expect(subject.dig('data', 'instanceStatisticsMeasurements')).to be_nil - end - end - - context 'when user is an admin' do - let(:user) { create(:user, :admin) } - - before do - stub_feature_flags(user_mode_in_session: false) - end - - it 'returns data' do - expect(subject.dig('data', 'instanceStatisticsMeasurements', 'nodes')).not_to be_empty - end - end - end -end diff --git a/spec/graphql/types/admin/analytics/usage_trends/measurement_identifier_enum_spec.rb b/spec/graphql/types/admin/analytics/usage_trends/measurement_identifier_enum_spec.rb new file mode 100644 index 00000000000..91851c11dc8 --- /dev/null +++ b/spec/graphql/types/admin/analytics/usage_trends/measurement_identifier_enum_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['MeasurementIdentifier'] do + specify { expect(described_class.graphql_name).to eq('MeasurementIdentifier') } + + it 'exposes all the existing identifier values' do + ee_only_identifiers = %w[billable_users] + identifiers = Analytics::UsageTrends::Measurement.identifiers.keys.reject do |x| + ee_only_identifiers.include?(x) + end.map(&:upcase) + + expect(described_class.values.keys).to match_array(identifiers) + end +end diff --git a/spec/graphql/types/admin/analytics/usage_trends/measurement_type_spec.rb b/spec/graphql/types/admin/analytics/usage_trends/measurement_type_spec.rb new file mode 100644 index 00000000000..c50092d7f0e --- /dev/null +++ b/spec/graphql/types/admin/analytics/usage_trends/measurement_type_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['UsageTrendsMeasurement'] do + subject { described_class } + + it { is_expected.to have_graphql_field(:recorded_at) } + it { is_expected.to have_graphql_field(:identifier) } + it { is_expected.to have_graphql_field(:count) } + + describe 'authorization' do + let_it_be(:measurement) { create(:usage_trends_measurement, :project_count) } + let(:user) { create(:user) } + + let(:query) do + <<~GRAPHQL + query usageTrendsMeasurements($identifier: MeasurementIdentifier!) { + usageTrendsMeasurements(identifier: $identifier) { + nodes { + count + identifier + } + } + } + GRAPHQL + end + + subject do + GitlabSchema.execute( + query, + variables: { identifier: 'PROJECTS' }, + context: { current_user: user } + ).to_h + end + + context 'when the user is not admin' do + it 'returns no data' do + expect(subject.dig('data', 'usageTrendsMeasurements')).to be_nil + end + end + + context 'when user is an admin' do + let(:user) { create(:user, :admin) } + + before do + stub_feature_flags(user_mode_in_session: false) + end + + it 'returns data' do + expect(subject.dig('data', 'usageTrendsMeasurements', 'nodes')).not_to be_empty + end + 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 -- cgit v1.2.3