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:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-06-18 14:18:50 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-06-18 14:18:50 +0300
commit8c7f4e9d5f36cff46365a7f8c4b9c21578c1e781 (patch)
treea77e7fe7a93de11213032ed4ab1f33a3db51b738 /spec/graphql
parent00b35af3db1abfe813a778f643dad221aad51fca (diff)
Add latest changes from gitlab-org/gitlab@13-1-stable-ee
Diffstat (limited to 'spec/graphql')
-rw-r--r--spec/graphql/features/authorization_spec.rb2
-rw-r--r--spec/graphql/features/feature_flag_spec.rb2
-rw-r--r--spec/graphql/gitlab_schema_spec.rb8
-rw-r--r--spec/graphql/mutations/alert_management/alerts/set_assignees_spec.rb167
-rw-r--r--spec/graphql/mutations/alert_management/create_alert_issue_spec.rb2
-rw-r--r--spec/graphql/mutations/alert_management/update_alert_status_spec.rb4
-rw-r--r--spec/graphql/mutations/branches/create_spec.rb2
-rw-r--r--spec/graphql/mutations/commits/create_spec.rb180
-rw-r--r--spec/graphql/mutations/concerns/mutations/resolves_group_spec.rb2
-rw-r--r--spec/graphql/mutations/concerns/mutations/resolves_issuable_spec.rb2
-rw-r--r--spec/graphql/mutations/concerns/mutations/resolves_project_spec.rb22
-rw-r--r--spec/graphql/mutations/container_expiration_policies/update_spec.rb96
-rw-r--r--spec/graphql/mutations/design_management/delete_spec.rb2
-rw-r--r--spec/graphql/mutations/design_management/upload_spec.rb2
-rw-r--r--spec/graphql/mutations/discussions/toggle_resolve_spec.rb155
-rw-r--r--spec/graphql/mutations/issues/set_confidential_spec.rb2
-rw-r--r--spec/graphql/mutations/issues/set_due_date_spec.rb2
-rw-r--r--spec/graphql/mutations/merge_requests/create_spec.rb87
-rw-r--r--spec/graphql/resolvers/alert_management/alert_resolver_spec.rb (renamed from spec/graphql/resolvers/alert_management_alert_resolver_spec.rb)6
-rw-r--r--spec/graphql/resolvers/base_resolver_spec.rb52
-rw-r--r--spec/graphql/resolvers/concerns/looks_ahead_spec.rb177
-rw-r--r--spec/graphql/resolvers/concerns/resolves_project_spec.rb37
-rw-r--r--spec/graphql/resolvers/merge_requests_resolver_spec.rb157
-rw-r--r--spec/graphql/resolvers/project_members_resolver_spec.rb62
-rw-r--r--spec/graphql/resolvers/project_pipeline_resolver_spec.rb36
-rw-r--r--spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb10
-rw-r--r--spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb81
-rw-r--r--spec/graphql/resolvers/user_resolver_spec.rb45
-rw-r--r--spec/graphql/resolvers/users_resolver_spec.rb51
-rw-r--r--spec/graphql/types/access_level_enum_spec.rb11
-rw-r--r--spec/graphql/types/access_level_type_spec.rb13
-rw-r--r--spec/graphql/types/alert_management/alert_type_spec.rb3
-rw-r--r--spec/graphql/types/base_field_spec.rb19
-rw-r--r--spec/graphql/types/commit_action_mode_enum_spec.rb11
-rw-r--r--spec/graphql/types/commit_encoding_enum_spec.rb11
-rw-r--r--spec/graphql/types/container_expiration_policy_cadence_enum_spec.rb9
-rw-r--r--spec/graphql/types/container_expiration_policy_keep_enum_spec.rb9
-rw-r--r--spec/graphql/types/container_expiration_policy_older_than_enum_spec.rb9
-rw-r--r--spec/graphql/types/container_expiration_policy_type_spec.rb27
-rw-r--r--spec/graphql/types/evidence_type_spec.rb15
-rw-r--r--spec/graphql/types/group_member_type_spec.rb19
-rw-r--r--spec/graphql/types/group_type_spec.rb2
-rw-r--r--spec/graphql/types/jira_import_type_spec.rb5
-rw-r--r--spec/graphql/types/merge_request_type_spec.rb5
-rw-r--r--spec/graphql/types/metrics/dashboard_type_spec.rb2
-rw-r--r--spec/graphql/types/notes/diff_position_type_spec.rb17
-rw-r--r--spec/graphql/types/notes/discussion_type_spec.rb16
-rw-r--r--spec/graphql/types/notes/note_type_spec.rb22
-rw-r--r--spec/graphql/types/notes/noteable_type_spec.rb11
-rw-r--r--spec/graphql/types/project_member_type_spec.rb19
-rw-r--r--spec/graphql/types/project_type_spec.rb34
-rw-r--r--spec/graphql/types/projects/jira_project_type_spec.rb11
-rw-r--r--spec/graphql/types/projects/jira_service_type_spec.rb2
-rw-r--r--spec/graphql/types/query_type_spec.rb13
-rw-r--r--spec/graphql/types/release_assets_type_spec.rb27
-rw-r--r--spec/graphql/types/release_links_type_spec.rb15
-rw-r--r--spec/graphql/types/release_source_type_spec.rb15
-rw-r--r--spec/graphql/types/release_type_spec.rb14
-rw-r--r--spec/graphql/types/resolvable_interface_spec.rb16
-rw-r--r--spec/graphql/types/snippet_type_spec.rb114
-rw-r--r--spec/graphql/types/snippets/file_input_action_enum_spec.rb11
-rw-r--r--spec/graphql/types/snippets/file_input_type_spec.rb15
-rw-r--r--spec/graphql/types/user_type_spec.rb14
63 files changed, 1871 insertions, 138 deletions
diff --git a/spec/graphql/features/authorization_spec.rb b/spec/graphql/features/authorization_spec.rb
index 4f54695e5be..6e5a8b9f4be 100644
--- a/spec/graphql/features/authorization_spec.rb
+++ b/spec/graphql/features/authorization_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Gitlab::Graphql::Authorization' do
+RSpec.describe 'Gitlab::Graphql::Authorization' do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
diff --git a/spec/graphql/features/feature_flag_spec.rb b/spec/graphql/features/feature_flag_spec.rb
index 51914cf0ca8..b484663d675 100644
--- a/spec/graphql/features/feature_flag_spec.rb
+++ b/spec/graphql/features/feature_flag_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Graphql Field feature flags' do
+RSpec.describe 'Graphql Field feature flags' do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
diff --git a/spec/graphql/gitlab_schema_spec.rb b/spec/graphql/gitlab_schema_spec.rb
index 8960ad91543..5d6aa863994 100644
--- a/spec/graphql/gitlab_schema_spec.rb
+++ b/spec/graphql/gitlab_schema_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema do
+RSpec.describe GitlabSchema do
let_it_be(:connections) { GitlabSchema.connections.all_wrappers }
let(:user) { build :user }
@@ -46,12 +46,6 @@ describe GitlabSchema do
expect(connection).to eq(Gitlab::Graphql::Pagination::ExternallyPaginatedArrayConnection)
end
- it 'paginates FilterableArray using `Pagination::FilterableArrayConnection`' do
- connection = connections[Gitlab::Graphql::FilterableArray]
-
- expect(connection).to eq(Gitlab::Graphql::Pagination::FilterableArrayConnection)
- end
-
describe '.execute' do
context 'for different types of users' do
context 'when no context' do
diff --git a/spec/graphql/mutations/alert_management/alerts/set_assignees_spec.rb b/spec/graphql/mutations/alert_management/alerts/set_assignees_spec.rb
new file mode 100644
index 00000000000..a025b3d344a
--- /dev/null
+++ b/spec/graphql/mutations/alert_management/alerts/set_assignees_spec.rb
@@ -0,0 +1,167 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Mutations::AlertManagement::Alerts::SetAssignees do
+ let_it_be(:starting_assignee) { create(:user) }
+ let_it_be(:unassigned_user) { create(:user) }
+ let_it_be(:alert) { create(:alert_management_alert, assignees: [starting_assignee]) }
+ let_it_be(:project) { alert.project }
+
+ let(:current_user) { starting_assignee }
+ let(:assignee_usernames) { [unassigned_user.username] }
+ let(:operation_mode) { nil }
+
+ let(:args) do
+ {
+ project_path: project.full_path,
+ iid: alert.iid,
+ assignee_usernames: assignee_usernames,
+ operation_mode: operation_mode
+ }
+ end
+
+ before_all do
+ project.add_developer(starting_assignee)
+ project.add_developer(unassigned_user)
+ end
+
+ specify { expect(described_class).to require_graphql_authorizations(:update_alert_management_alert) }
+
+ describe '#resolve' do
+ let(:expected_assignees) { [unassigned_user] }
+
+ subject(:resolve) { mutation_for(project, current_user).resolve(args) }
+
+ shared_examples 'successful resolution' do
+ after do
+ alert.assignees = [starting_assignee]
+ end
+
+ it 'successfully resolves' do
+ expect(resolve).to eq(alert: alert.reload, errors: [])
+ expect(alert.assignees).to eq(expected_assignees)
+ end
+ end
+
+ shared_examples 'noop' do
+ it 'makes no changes' do
+ original_assignees = alert.assignees
+
+ expect(resolve).to eq(alert: alert.reload, errors: [])
+ expect(alert.assignees).to eq(original_assignees)
+ end
+ end
+
+ context 'when operation mode is not specified' do
+ it_behaves_like 'successful resolution'
+ end
+
+ context 'when user does not have permission to update alerts' do
+ let(:current_user) { create(:user) }
+
+ it 'raises an error if the resource is not accessible to the user' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+
+ context 'for APPEND operation' do
+ let(:operation_mode) { Types::MutationOperationModeEnum.enum[:append] }
+
+ # Only allow a single assignee
+ context 'when a different user is already assigned' do
+ it_behaves_like 'noop'
+ end
+
+ context 'when no users are specified' do
+ let(:assignee_usernames) { [] }
+
+ it_behaves_like 'noop'
+ end
+
+ context 'when a user is specified and no user is assigned' do
+ before do
+ alert.assignees = []
+ end
+
+ it_behaves_like 'successful resolution'
+ end
+
+ context 'when the specified user is already assigned to the alert' do
+ let(:assignee_usernames) { [starting_assignee.username] }
+
+ it_behaves_like 'noop'
+ end
+ end
+
+ context 'for REPLACE operation' do
+ let(:operation_mode) { Types::MutationOperationModeEnum.enum[:replace] }
+
+ context 'when a different user is already assigned' do
+ it_behaves_like 'successful resolution'
+ end
+
+ context 'when no users are specified' do
+ let(:assignee_usernames) { [] }
+ let(:expected_assignees) { [] }
+
+ it_behaves_like 'successful resolution'
+ end
+
+ context 'when a user is specified and no user is assigned' do
+ before do
+ alert.assignees = []
+ end
+
+ it_behaves_like 'successful resolution'
+ end
+
+ context 'when the specified user is already assigned to the alert' do
+ let(:assignee_usernames) { [starting_assignee.username] }
+
+ it_behaves_like 'noop'
+ end
+
+ context 'when multiple users are specified' do
+ let(:assignees) { [starting_assignee, unassigned_user] }
+ let(:assignee_usernames) { assignees.map(&:username) }
+ let(:expected_assignees) { [assignees.last] }
+
+ it_behaves_like 'successful resolution'
+ end
+ end
+
+ context 'for REMOVE operation' do
+ let(:operation_mode) { Types::MutationOperationModeEnum.enum[:remove] }
+
+ context 'when a different user is already assigned' do
+ it_behaves_like 'noop'
+ end
+
+ context 'when no users are specified' do
+ let(:assignee_usernames) { [] }
+
+ it_behaves_like 'noop'
+ end
+
+ context 'when a user is specified and no user is assigned' do
+ before do
+ alert.assignees = []
+ end
+
+ it_behaves_like 'noop'
+ end
+
+ context 'when the specified user is already assigned to the alert' do
+ let(:assignee_usernames) { [starting_assignee.username] }
+ let(:expected_assignees) { [] }
+
+ it_behaves_like 'successful resolution'
+ end
+ end
+ end
+
+ def mutation_for(project, user)
+ described_class.new(object: project, context: { current_user: user }, field: nil)
+ end
+end
diff --git a/spec/graphql/mutations/alert_management/create_alert_issue_spec.rb b/spec/graphql/mutations/alert_management/create_alert_issue_spec.rb
index 1e51767cf0e..fa5a84b4fcc 100644
--- a/spec/graphql/mutations/alert_management/create_alert_issue_spec.rb
+++ b/spec/graphql/mutations/alert_management/create_alert_issue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::AlertManagement::CreateAlertIssue do
+RSpec.describe Mutations::AlertManagement::CreateAlertIssue do
let_it_be(:current_user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:alert) { create(:alert_management_alert, project: project, status: 'triggered') }
diff --git a/spec/graphql/mutations/alert_management/update_alert_status_spec.rb b/spec/graphql/mutations/alert_management/update_alert_status_spec.rb
index 8b9abd9497d..68513c02040 100644
--- a/spec/graphql/mutations/alert_management/update_alert_status_spec.rb
+++ b/spec/graphql/mutations/alert_management/update_alert_status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::AlertManagement::UpdateAlertStatus do
+RSpec.describe Mutations::AlertManagement::UpdateAlertStatus do
let_it_be(:current_user) { create(:user) }
let_it_be(:alert) { create(:alert_management_alert, :triggered) }
let_it_be(:project) { alert.project }
@@ -33,7 +33,7 @@ describe Mutations::AlertManagement::UpdateAlertStatus do
context 'error occurs when updating' do
it 'returns the alert with errors' do
# Stub an error on the alert
- allow_next_instance_of(Resolvers::AlertManagementAlertResolver) do |resolver|
+ allow_next_instance_of(Resolvers::AlertManagement::AlertResolver) do |resolver|
allow(resolver).to receive(:resolve).and_return(alert)
end
diff --git a/spec/graphql/mutations/branches/create_spec.rb b/spec/graphql/mutations/branches/create_spec.rb
index 744f8f1f2bc..e378a8e3d41 100644
--- a/spec/graphql/mutations/branches/create_spec.rb
+++ b/spec/graphql/mutations/branches/create_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::Branches::Create do
+RSpec.describe Mutations::Branches::Create do
subject(:mutation) { described_class.new(object: nil, context: context, field: nil) }
let_it_be(:project) { create(:project, :public, :repository) }
diff --git a/spec/graphql/mutations/commits/create_spec.rb b/spec/graphql/mutations/commits/create_spec.rb
new file mode 100644
index 00000000000..bb0b8c577b0
--- /dev/null
+++ b/spec/graphql/mutations/commits/create_spec.rb
@@ -0,0 +1,180 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::Commits::Create do
+ subject(:mutation) { described_class.new(object: nil, context: context, field: nil) }
+
+ let_it_be(:project) { create(:project, :public, :repository) }
+ let_it_be(:user) { create(:user) }
+ let(:context) do
+ GraphQL::Query::Context.new(
+ query: OpenStruct.new(schema: nil),
+ values: { current_user: user },
+ object: nil
+ )
+ end
+
+ specify { expect(described_class).to require_graphql_authorizations(:push_code) }
+
+ describe '#resolve' do
+ subject { mutation.resolve(project_path: project.full_path, branch: branch, message: message, actions: actions) }
+
+ let(:branch) { 'master' }
+ let(:message) { 'Commit message' }
+ let(:actions) do
+ [
+ {
+ action: 'create',
+ file_path: 'NEW_FILE.md',
+ content: 'Hello'
+ }
+ ]
+ end
+
+ let(:mutated_commit) { subject[:commit] }
+
+ it 'raises an error if the resource is not accessible to the user' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+
+ context 'when user does not have enough permissions' do
+ before do
+ project.add_guest(user)
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+
+ context 'when user is a maintainer of a different project' do
+ before do
+ create(:project_empty_repo).add_maintainer(user)
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+
+ context 'when the user can create a commit' do
+ let(:deltas) { mutated_commit.raw_deltas }
+
+ before_all do
+ project.add_developer(user)
+ end
+
+ context 'when service successfully creates a new commit' do
+ it 'returns a new commit' do
+ expect(mutated_commit).to have_attributes(message: message, project: project)
+ expect(subject[:errors]).to be_empty
+
+ expect_to_contain_deltas([
+ a_hash_including(a_mode: '0', b_mode: '100644', new_file: true, new_path: 'NEW_FILE.md')
+ ])
+ end
+ end
+
+ context 'when request has multiple actions' do
+ let(:actions) do
+ [
+ {
+ action: 'create',
+ file_path: 'foo/foobar',
+ content: 'some content'
+ },
+ {
+ action: 'delete',
+ file_path: 'README.md'
+ },
+ {
+ action: 'move',
+ file_path: "LICENSE.md",
+ previous_path: "LICENSE",
+ content: "some content"
+ },
+ {
+ action: 'update',
+ file_path: 'VERSION',
+ content: 'new content'
+ },
+ {
+ action: 'chmod',
+ file_path: 'CHANGELOG',
+ execute_filemode: true
+ }
+ ]
+ end
+
+ it 'returns a new commit' do
+ expect(mutated_commit).to have_attributes(message: message, project: project)
+ expect(subject[:errors]).to be_empty
+
+ expect_to_contain_deltas([
+ a_hash_including(a_mode: '0', b_mode: '100644', new_path: 'foo/foobar'),
+ a_hash_including(deleted_file: true, new_path: 'README.md'),
+ a_hash_including(deleted_file: true, new_path: 'LICENSE'),
+ a_hash_including(new_file: true, new_path: 'LICENSE.md'),
+ a_hash_including(new_file: false, new_path: 'VERSION'),
+ a_hash_including(a_mode: '100644', b_mode: '100755', new_path: 'CHANGELOG')
+ ])
+ end
+ end
+
+ context 'when actions are not defined' do
+ let(:actions) { [] }
+
+ it 'returns a new commit' do
+ expect(mutated_commit).to have_attributes(message: message, project: project)
+ expect(subject[:errors]).to be_empty
+
+ expect_to_contain_deltas([])
+ end
+ end
+
+ context 'when branch does not exist' do
+ let(:branch) { 'unknown' }
+
+ it 'returns errors' do
+ expect(mutated_commit).to be_nil
+ expect(subject[:errors]).to eq(['You can only create or edit files when you are on a branch'])
+ end
+ end
+
+ context 'when message is not set' do
+ let(:message) { nil }
+
+ it 'returns errors' do
+ expect(mutated_commit).to be_nil
+ expect(subject[:errors]).to eq(['3:UserCommitFiles: empty CommitMessage'])
+ end
+ end
+
+ context 'when actions are incorrect' do
+ let(:actions) { [{ action: 'unknown', file_path: 'test.md', content: '' }] }
+
+ it 'returns errors' do
+ expect(mutated_commit).to be_nil
+ expect(subject[:errors]).to eq(['Unknown action \'unknown\''])
+ end
+ end
+
+ context 'when branch is protected' do
+ before do
+ create(:protected_branch, project: project, name: branch)
+ end
+
+ it 'returns errors' do
+ expect(mutated_commit).to be_nil
+ expect(subject[:errors]).to eq(['You are not allowed to push into this branch'])
+ end
+ end
+ end
+ end
+
+ def expect_to_contain_deltas(expected_deltas)
+ expect(deltas.count).to eq(expected_deltas.count)
+ expect(deltas).to include(*expected_deltas)
+ end
+end
diff --git a/spec/graphql/mutations/concerns/mutations/resolves_group_spec.rb b/spec/graphql/mutations/concerns/mutations/resolves_group_spec.rb
index 51d3c4f5d6b..6bed3a752ed 100644
--- a/spec/graphql/mutations/concerns/mutations/resolves_group_spec.rb
+++ b/spec/graphql/mutations/concerns/mutations/resolves_group_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::ResolvesGroup do
+RSpec.describe Mutations::ResolvesGroup do
let(:mutation_class) do
Class.new(Mutations::BaseMutation) do
include Mutations::ResolvesGroup
diff --git a/spec/graphql/mutations/concerns/mutations/resolves_issuable_spec.rb b/spec/graphql/mutations/concerns/mutations/resolves_issuable_spec.rb
index 145e42e2a51..706a54931ea 100644
--- a/spec/graphql/mutations/concerns/mutations/resolves_issuable_spec.rb
+++ b/spec/graphql/mutations/concerns/mutations/resolves_issuable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::ResolvesIssuable do
+RSpec.describe Mutations::ResolvesIssuable do
let_it_be(:mutation_class) do
Class.new(Mutations::BaseMutation) do
include Mutations::ResolvesIssuable
diff --git a/spec/graphql/mutations/concerns/mutations/resolves_project_spec.rb b/spec/graphql/mutations/concerns/mutations/resolves_project_spec.rb
deleted file mode 100644
index b5c349f6284..00000000000
--- a/spec/graphql/mutations/concerns/mutations/resolves_project_spec.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Mutations::ResolvesProject do
- let(:mutation_class) do
- Class.new(Mutations::BaseMutation) do
- include Mutations::ResolvesProject
- end
- end
-
- let(:context) { double }
-
- subject(:mutation) { mutation_class.new(object: nil, context: context, field: nil) }
-
- it 'uses the ProjectsResolver to resolve projects by path' do
- project = create(:project)
-
- expect(Resolvers::ProjectResolver).to receive(:new).with(object: nil, context: context, field: nil).and_call_original
- expect(mutation.resolve_project(full_path: project.full_path).sync).to eq(project)
- end
-end
diff --git a/spec/graphql/mutations/container_expiration_policies/update_spec.rb b/spec/graphql/mutations/container_expiration_policies/update_spec.rb
new file mode 100644
index 00000000000..fc90f437576
--- /dev/null
+++ b/spec/graphql/mutations/container_expiration_policies/update_spec.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Mutations::ContainerExpirationPolicies::Update do
+ using RSpec::Parameterized::TableSyntax
+
+ let_it_be(:project, reload: true) { create(:project) }
+ let_it_be(:user) { create(:user) }
+
+ let(:container_expiration_policy) { project.container_expiration_policy }
+ let(:params) { { project_path: project.full_path, cadence: '3month', keep_n: 100, older_than: '14d' } }
+
+ specify { expect(described_class).to require_graphql_authorizations(:destroy_container_image) }
+
+ describe '#resolve' do
+ subject { described_class.new(object: project, context: { current_user: user }, field: nil).resolve(params) }
+
+ RSpec.shared_examples 'returning a success' do
+ it 'returns the container expiration policy with no errors' do
+ expect(subject).to eq(
+ container_expiration_policy: container_expiration_policy,
+ errors: []
+ )
+ end
+ end
+
+ RSpec.shared_examples 'updating the container expiration policy' do
+ it_behaves_like 'updating the container expiration policy attributes', mode: :update, from: { cadence: '1d', keep_n: 10, older_than: '90d' }, to: { cadence: '3month', keep_n: 100, older_than: '14d' }
+
+ it_behaves_like 'returning a success'
+
+ context 'with invalid params' do
+ let_it_be(:params) { { project_path: project.full_path, cadence: '20d' } }
+
+ it_behaves_like 'not creating the container expiration policy'
+
+ it "doesn't update the cadence" do
+ expect { subject }
+ .not_to change { container_expiration_policy.reload.cadence }
+ end
+
+ it 'returns an error' do
+ expect(subject).to eq(
+ container_expiration_policy: nil,
+ errors: ['Cadence is not included in the list']
+ )
+ end
+ end
+ end
+
+ RSpec.shared_examples 'denying access to container expiration policy' do
+ it 'raises Gitlab::Graphql::Errors::ResourceNotAvailable' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+
+ context 'with existing container expiration policy' do
+ where(:user_role, :shared_examples_name) do
+ :maintainer | 'updating the container expiration policy'
+ :developer | 'updating the container expiration policy'
+ :reporter | 'denying access to container expiration policy'
+ :guest | 'denying access to container expiration policy'
+ :anonymous | 'denying access to container expiration policy'
+ end
+
+ with_them do
+ before do
+ project.send("add_#{user_role}", user) unless user_role == :anonymous
+ end
+
+ it_behaves_like params[:shared_examples_name]
+ end
+ end
+
+ context 'without existing container expiration policy' do
+ let_it_be(:project, reload: true) { create(:project, :without_container_expiration_policy) }
+
+ where(:user_role, :shared_examples_name) do
+ :maintainer | 'creating the container expiration policy'
+ :developer | 'creating the container expiration policy'
+ :reporter | 'denying access to container expiration policy'
+ :guest | 'denying access to container expiration policy'
+ :anonymous | 'denying access to container expiration policy'
+ end
+
+ with_them do
+ before do
+ project.send("add_#{user_role}", user) unless user_role == :anonymous
+ end
+
+ it_behaves_like params[:shared_examples_name]
+ end
+ end
+ end
+end
diff --git a/spec/graphql/mutations/design_management/delete_spec.rb b/spec/graphql/mutations/design_management/delete_spec.rb
index 60be6dad62a..3efa865c64b 100644
--- a/spec/graphql/mutations/design_management/delete_spec.rb
+++ b/spec/graphql/mutations/design_management/delete_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::DesignManagement::Delete do
+RSpec.describe Mutations::DesignManagement::Delete do
include DesignManagementTestHelpers
let(:issue) { create(:issue) }
diff --git a/spec/graphql/mutations/design_management/upload_spec.rb b/spec/graphql/mutations/design_management/upload_spec.rb
index 783af70448c..326d88cea80 100644
--- a/spec/graphql/mutations/design_management/upload_spec.rb
+++ b/spec/graphql/mutations/design_management/upload_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Mutations::DesignManagement::Upload do
+RSpec.describe Mutations::DesignManagement::Upload do
include DesignManagementTestHelpers
include ConcurrentHelpers
diff --git a/spec/graphql/mutations/discussions/toggle_resolve_spec.rb b/spec/graphql/mutations/discussions/toggle_resolve_spec.rb
new file mode 100644
index 00000000000..9ac4d6ab165
--- /dev/null
+++ b/spec/graphql/mutations/discussions/toggle_resolve_spec.rb
@@ -0,0 +1,155 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::Discussions::ToggleResolve do
+ subject(:mutation) do
+ described_class.new(object: nil, context: { current_user: user }, field: nil)
+ end
+
+ let_it_be(:project) { create(:project, :repository) }
+
+ describe '#resolve' do
+ subject do
+ mutation.resolve({ id: id_arg, resolve: resolve_arg })
+ end
+
+ let(:id_arg) { discussion.to_global_id.to_s }
+ let(:resolve_arg) { true }
+ let(:mutated_discussion) { subject[:discussion] }
+ let(:errors) { subject[:errors] }
+
+ shared_examples 'a working resolve method' do
+ context 'when the user does not have permission' do
+ let_it_be(:user) { create(:user) }
+
+ it 'raises an error if the resource is not accessible to the user' do
+ expect { subject }.to raise_error(
+ Gitlab::Graphql::Errors::ResourceNotAvailable,
+ "The resource that you are attempting to access does not exist or you don't have permission to perform this action"
+ )
+ end
+ end
+
+ context 'when the user has permission' do
+ let_it_be(:user) { create(:user, developer_projects: [project]) }
+
+ context 'when discussion cannot be found' do
+ let(:id_arg) { "#{discussion.to_global_id}foo" }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(
+ Gitlab::Graphql::Errors::ResourceNotAvailable,
+ "The resource that you are attempting to access does not exist or you don't have permission to perform this action"
+ )
+ end
+ end
+
+ context 'when discussion is not a Discussion' do
+ let(:discussion) { create(:note, noteable: noteable, project: project) }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(
+ Gitlab::Graphql::Errors::ArgumentError,
+ "#{discussion.to_global_id} is not a valid id for Discussion."
+ )
+ end
+ end
+
+ shared_examples 'returns a resolved discussion without errors' do
+ it 'returns a resolved discussion' do
+ expect(mutated_discussion).to be_resolved
+ end
+
+ it 'returns empty errors' do
+ expect(errors).to be_empty
+ end
+ end
+
+ shared_examples 'returns an unresolved discussion without errors' do
+ it 'returns an unresolved discussion' do
+ expect(mutated_discussion).not_to be_resolved
+ end
+
+ it 'returns empty errors' do
+ expect(errors).to be_empty
+ end
+ end
+
+ context 'when the `resolve` argument is true' do
+ include_examples 'returns a resolved discussion without errors'
+
+ context 'when the discussion is already resolved' do
+ before do
+ discussion.resolve!(user)
+ end
+
+ include_examples 'returns a resolved discussion without errors'
+ end
+
+ context 'when the service raises an `ActiveRecord::RecordNotSaved` error' do
+ before do
+ allow_next_instance_of(::Discussions::ResolveService) do |service|
+ allow(service).to receive(:execute).and_raise(ActiveRecord::RecordNotSaved)
+ end
+ end
+
+ it 'does not resolve the discussion' do
+ expect(mutated_discussion).not_to be_resolved
+ end
+
+ it 'returns errors' do
+ expect(errors).to contain_exactly('Discussion failed to be resolved')
+ end
+ end
+ end
+
+ context 'when the `resolve` argument is false' do
+ let(:resolve_arg) { false }
+
+ context 'when the discussion is resolved' do
+ before do
+ discussion.resolve!(user)
+ end
+
+ include_examples 'returns an unresolved discussion without errors'
+
+ context 'when the service raises an `ActiveRecord::RecordNotSaved` error' do
+ before do
+ allow_next_instance_of(discussion.class) do |instance|
+ allow(instance).to receive(:unresolve!).and_raise(ActiveRecord::RecordNotSaved)
+ end
+ end
+
+ it 'does not unresolve the discussion' do
+ expect(mutated_discussion).to be_resolved
+ end
+
+ it 'returns errors' do
+ expect(errors).to contain_exactly('Discussion failed to be unresolved')
+ end
+ end
+ end
+
+ context 'when the discussion is already unresolved' do
+ include_examples 'returns an unresolved discussion without errors'
+ end
+ end
+ end
+ end
+
+ context 'when discussion is on a merge request' do
+ let_it_be(:noteable) { create(:merge_request, source_project: project) }
+ let(:discussion) { create(:diff_note_on_merge_request, noteable: noteable, project: project).to_discussion }
+
+ it_behaves_like 'a working resolve method'
+ end
+
+ context 'when discussion is on a design' do
+ let_it_be(:noteable) { create(:design, :with_file, issue: create(:issue, project: project)) }
+ let(:discussion) { create(:diff_note_on_design, noteable: noteable, project: project).to_discussion }
+
+ it_behaves_like 'a working resolve method'
+ end
+ end
+end
diff --git a/spec/graphql/mutations/issues/set_confidential_spec.rb b/spec/graphql/mutations/issues/set_confidential_spec.rb
index c90ce2658d6..820f9aa5e17 100644
--- a/spec/graphql/mutations/issues/set_confidential_spec.rb
+++ b/spec/graphql/mutations/issues/set_confidential_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::Issues::SetConfidential do
+RSpec.describe Mutations::Issues::SetConfidential do
let(:issue) { create(:issue) }
let(:user) { create(:user) }
diff --git a/spec/graphql/mutations/issues/set_due_date_spec.rb b/spec/graphql/mutations/issues/set_due_date_spec.rb
index 84df6fce7c7..a638971d966 100644
--- a/spec/graphql/mutations/issues/set_due_date_spec.rb
+++ b/spec/graphql/mutations/issues/set_due_date_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::Issues::SetDueDate do
+RSpec.describe Mutations::Issues::SetDueDate do
let(:issue) { create(:issue) }
let(:user) { create(:user) }
diff --git a/spec/graphql/mutations/merge_requests/create_spec.rb b/spec/graphql/mutations/merge_requests/create_spec.rb
new file mode 100644
index 00000000000..88acd3ed5b6
--- /dev/null
+++ b/spec/graphql/mutations/merge_requests/create_spec.rb
@@ -0,0 +1,87 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Mutations::MergeRequests::Create do
+ subject(:mutation) { described_class.new(object: nil, context: context, field: nil) }
+
+ let_it_be(:project) { create(:project, :public, :repository) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:context) do
+ GraphQL::Query::Context.new(
+ query: OpenStruct.new(schema: nil),
+ values: { current_user: user },
+ object: nil
+ )
+ end
+
+ describe '#resolve' do
+ subject do
+ mutation.resolve(
+ project_path: project.full_path,
+ title: title,
+ source_branch: source_branch,
+ target_branch: target_branch,
+ description: description
+ )
+ end
+
+ let(:title) { 'MergeRequest' }
+ let(:source_branch) { 'feature' }
+ let(:target_branch) { 'master' }
+ let(:description) { nil }
+
+ let(:mutated_merge_request) { subject[:merge_request] }
+
+ it 'raises an error if the resource is not accessible to the user' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+
+ context 'when user does not have enough permissions to create a merge request' do
+ before do
+ project.add_guest(user)
+ end
+
+ it 'raises an error if the resource is not accessible to the user' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+
+ context 'when the user can create a merge request' do
+ before_all do
+ project.add_developer(user)
+ end
+
+ it 'creates a new merge request' do
+ expect { mutated_merge_request }.to change(MergeRequest, :count).by(1)
+ end
+
+ it 'returns a new merge request' do
+ expect(mutated_merge_request.title).to eq(title)
+ expect(subject[:errors]).to be_empty
+ end
+
+ context 'when optional description field is set' do
+ let(:description) { 'content' }
+
+ it 'returns a new merge request with a description' do
+ expect(mutated_merge_request.description).to eq(description)
+ expect(subject[:errors]).to be_empty
+ end
+ end
+
+ context 'when service cannot create a merge request' do
+ let(:title) { nil }
+
+ it 'does not create a new merge request' do
+ expect { mutated_merge_request }.not_to change(MergeRequest, :count)
+ end
+
+ it 'returns errors' do
+ expect(mutated_merge_request).to be_nil
+ expect(subject[:errors]).to eq(['Title can\'t be blank'])
+ end
+ end
+ end
+ end
+end
diff --git a/spec/graphql/resolvers/alert_management_alert_resolver_spec.rb b/spec/graphql/resolvers/alert_management/alert_resolver_spec.rb
index 971a81a826d..6c12f765e69 100644
--- a/spec/graphql/resolvers/alert_management_alert_resolver_spec.rb
+++ b/spec/graphql/resolvers/alert_management/alert_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::AlertManagementAlertResolver do
+describe Resolvers::AlertManagement::AlertResolver do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
@@ -45,11 +45,11 @@ describe Resolvers::AlertManagementAlertResolver do
let_it_be(:alert_count_3) { create(:alert_management_alert, project: project, events: 3) }
it 'sorts alerts ascending' do
- expect(resolve_alerts(sort: :events_count_asc)).to eq [alert_2, alert_1, alert_count_3, alert_count_6]
+ expect(resolve_alerts(sort: :event_count_asc)).to eq [alert_2, alert_1, alert_count_3, alert_count_6]
end
it 'sorts alerts descending' do
- expect(resolve_alerts(sort: :events_count_desc)).to eq [alert_count_6, alert_count_3, alert_1, alert_2]
+ expect(resolve_alerts(sort: :event_count_desc)).to eq [alert_count_6, alert_count_3, alert_1, alert_2]
end
end
end
diff --git a/spec/graphql/resolvers/base_resolver_spec.rb b/spec/graphql/resolvers/base_resolver_spec.rb
index 0a21b2797ee..6c384349577 100644
--- a/spec/graphql/resolvers/base_resolver_spec.rb
+++ b/spec/graphql/resolvers/base_resolver_spec.rb
@@ -41,9 +41,35 @@ describe Resolvers::BaseResolver do
end
end
+ context 'when the resolver returns early' do
+ let(:resolver) do
+ Class.new(described_class) do
+ def ready?(**args)
+ [false, %w(early return)]
+ end
+
+ def resolve(**args)
+ raise 'Should not get here'
+ end
+ end
+ end
+
+ it 'runs correctly in our test framework' do
+ expect(resolve(resolver)).to contain_exactly('early', 'return')
+ end
+
+ it 'single selects the first early return value' do
+ expect(resolve(resolver.single)).to eq('early')
+ end
+
+ it 'last selects the last early return value' do
+ expect(resolve(resolver.last)).to eq('return')
+ end
+ end
+
describe '.last' do
it 'returns a subclass from the resolver' do
- expect(last_resolver.last.superclass).to eq(last_resolver)
+ expect(last_resolver.last.ancestors).to include(last_resolver)
end
it 'returns the same subclass every time' do
@@ -95,4 +121,28 @@ describe Resolvers::BaseResolver do
end
end
end
+
+ describe '#synchronized_object' do
+ let(:object) { double(foo: :the_foo) }
+
+ let(:resolver) do
+ Class.new(described_class) do
+ def resolve(**args)
+ [synchronized_object.foo]
+ end
+ end
+ end
+
+ it 'handles raw objects' do
+ expect(resolve(resolver, obj: object)).to contain_exactly(:the_foo)
+ end
+
+ it 'handles lazy objects' do
+ delayed = BatchLoader::GraphQL.for(1).batch do |_, loader|
+ loader.call(1, object)
+ end
+
+ expect(resolve(resolver, obj: delayed)).to contain_exactly(:the_foo)
+ end
+ end
end
diff --git a/spec/graphql/resolvers/concerns/looks_ahead_spec.rb b/spec/graphql/resolvers/concerns/looks_ahead_spec.rb
new file mode 100644
index 00000000000..8b83f887846
--- /dev/null
+++ b/spec/graphql/resolvers/concerns/looks_ahead_spec.rb
@@ -0,0 +1,177 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe LooksAhead do
+ include GraphqlHelpers
+
+ let_it_be(:the_user) { create(:user) }
+ let_it_be(:label_a) { create(:label) }
+ let_it_be(:label_b) { create(:label) }
+ let_it_be(:issue_a) { create(:issue, author: the_user, labels: [label_a, label_b]) }
+ let_it_be(:issue_b) { create(:issue, author: the_user, labels: [label_a]) }
+ let_it_be(:issue_c) { create(:issue, author: the_user, labels: [label_b]) }
+
+ # Simplified schema to test lookahead
+ let_it_be(:schema) do
+ issues_resolver = Class.new(Resolvers::BaseResolver) do
+ include LooksAhead
+
+ def resolve_with_lookahead(**args)
+ apply_lookahead(object.issues)
+ end
+
+ def preloads
+ { labels: [:labels] }
+ end
+ end
+
+ label = Class.new(GraphQL::Schema::Object) do
+ graphql_name 'Label'
+ field :id, Integer, null: false
+ end
+ issue = Class.new(GraphQL::Schema::Object) do
+ graphql_name 'Issue'
+ field :title, String, null: true
+ field :labels, label.connection_type, null: true
+ end
+ user = Class.new(GraphQL::Schema::Object) do
+ graphql_name 'User'
+ field :name, String, null: true
+ field :issues, issue.connection_type,
+ null: true
+ field :issues_with_lookahead, issue.connection_type,
+ extras: [:lookahead],
+ resolver: issues_resolver,
+ null: true
+ end
+
+ Class.new(GraphQL::Schema) do
+ query(Class.new(GraphQL::Schema::Object) do
+ graphql_name 'Query'
+ field :find_user, user, null: true do
+ argument :username, String, required: true
+ end
+
+ def find_user(username:)
+ context[:user_db].find { |u| u.username == username }
+ end
+ end)
+ end
+ end
+
+ def query(doc = document)
+ GraphQL::Query.new(schema,
+ document: doc,
+ context: { user_db: [the_user] },
+ variables: { username: the_user.username })
+ end
+
+ let(:document) do
+ GraphQL.parse <<-GRAPHQL
+ query($username: String!){
+ findUser(username: $username) {
+ name
+ issues {
+ nodes {
+ title
+ labels { nodes { id } }
+ }
+ }
+ issuesWithLookahead {
+ nodes {
+ title
+ labels { nodes { id } }
+ }
+ }
+ }
+ }
+ GRAPHQL
+ end
+
+ def run_query(gql_query)
+ query(GraphQL.parse(gql_query)).result
+ end
+
+ shared_examples 'a working query on the test schema' do
+ it 'has a good test setup', :aggregate_failures do
+ expected_label_ids = [label_a, label_b].cycle.take(4).map(&:id)
+ issue_titles = [issue_a, issue_b, issue_c].map(&:title)
+
+ res = query.result
+
+ expect(res['errors']).to be_blank
+ expect(res.dig('data', 'findUser', 'name')).to eq(the_user.name)
+ %w(issues issuesWithLookahead).each do |field|
+ expect(all_issue_titles(res, field)).to match_array(issue_titles)
+ expect(all_label_ids(res, field)).to match_array(expected_label_ids)
+ end
+ end
+ end
+
+ it_behaves_like 'a working query on the test schema'
+
+ it 'preloads labels on issues' do
+ expect(the_user.issues).to receive(:preload).with(:labels)
+
+ query.result
+ end
+
+ context 'the feature flag is off' do
+ before do
+ stub_feature_flags(described_class::FEATURE_FLAG => false)
+ end
+
+ it_behaves_like 'a working query on the test schema'
+
+ it 'does not preload labels on issues' do
+ expect(the_user.issues).not_to receive(:preload).with(:labels)
+
+ query.result
+ end
+ end
+
+ it 'issues fewer queries than the naive approach' do
+ the_user.reload # ensure no attributes are loaded before we begin
+ naive = <<-GQL
+ query($username: String!){
+ findUser(username: $username) {
+ name
+ issues {
+ nodes {
+ labels { nodes { id } }
+ }
+ }
+ }
+ }
+ GQL
+ with_lookahead = <<-GQL
+ query($username: String!){
+ findUser(username: $username) {
+ name
+ issuesWithLookahead {
+ nodes {
+ labels { nodes { id } }
+ }
+ }
+ }
+ }
+ GQL
+
+ expect { run_query(with_lookahead) }.to issue_fewer_queries_than { run_query(naive) }
+ end
+
+ private
+
+ def all_label_ids(result, field_name)
+ result.dig('data', 'findUser', field_name, 'nodes').flat_map do |node|
+ node.dig('labels', 'nodes').map { |n| n['id'] }
+ end
+ end
+
+ def all_issue_titles(result, field_name)
+ result.dig('data', 'findUser', field_name, 'nodes').map do |node|
+ node['title']
+ end
+ end
+end
diff --git a/spec/graphql/resolvers/concerns/resolves_project_spec.rb b/spec/graphql/resolvers/concerns/resolves_project_spec.rb
new file mode 100644
index 00000000000..f29f54483d6
--- /dev/null
+++ b/spec/graphql/resolvers/concerns/resolves_project_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ResolvesProject do
+ include GraphqlHelpers
+
+ let(:implementing_class) do
+ Class.new do
+ include ResolvesProject
+ end
+ end
+
+ subject(:instance) { implementing_class.new }
+
+ let_it_be(:project) { create(:project) }
+
+ it 'can resolve projects by path' do
+ expect(sync(instance.resolve_project(full_path: project.full_path))).to eq(project)
+ end
+
+ it 'can resolve projects by id' do
+ expect(sync(instance.resolve_project(project_id: global_id_of(project)))).to eq(project)
+ end
+
+ it 'complains when both are present' do
+ expect do
+ instance.resolve_project(full_path: project.full_path, project_id: global_id_of(project))
+ end.to raise_error(::Gitlab::Graphql::Errors::ArgumentError)
+ end
+
+ it 'complains when neither is present' do
+ expect do
+ instance.resolve_project(full_path: nil, project_id: nil)
+ end.to raise_error(::Gitlab::Graphql::Errors::ArgumentError)
+ end
+end
diff --git a/spec/graphql/resolvers/merge_requests_resolver_spec.rb b/spec/graphql/resolvers/merge_requests_resolver_spec.rb
index 4217d257ab3..6ff7e1ecac6 100644
--- a/spec/graphql/resolvers/merge_requests_resolver_spec.rb
+++ b/spec/graphql/resolvers/merge_requests_resolver_spec.rb
@@ -6,61 +6,164 @@ describe Resolvers::MergeRequestsResolver do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository) }
- let_it_be(:merge_request_1) { create(:merge_request, :simple, source_project: project, target_project: project) }
- let_it_be(:merge_request_2) { create(:merge_request, :rebased, source_project: project, target_project: project) }
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:common_attrs) { { author: current_user, source_project: project, target_project: project } }
+ let_it_be(:merge_request_1) { create(:merge_request, :simple, **common_attrs) }
+ let_it_be(:merge_request_2) { create(:merge_request, :rebased, **common_attrs) }
+ let_it_be(:merge_request_3) { create(:merge_request, :unique_branches, **common_attrs) }
+ let_it_be(:merge_request_4) { create(:merge_request, :unique_branches, :locked, **common_attrs) }
+ let_it_be(:merge_request_5) { create(:merge_request, :simple, :locked, **common_attrs) }
+ let_it_be(:merge_request_6) { create(:labeled_merge_request, :unique_branches, labels: create_list(:label, 2), **common_attrs) }
let_it_be(:other_project) { create(:project, :repository) }
let_it_be(:other_merge_request) { create(:merge_request, source_project: other_project, target_project: other_project) }
let(:iid_1) { merge_request_1.iid }
let(:iid_2) { merge_request_2.iid }
let(:other_iid) { other_merge_request.iid }
+ before do
+ project.add_developer(current_user)
+ end
+
describe '#resolve' do
- it 'batch-resolves by target project full path and individual IID' do
- result = batch_sync(max_queries: 2) do
- resolve_mr(project, iid: iid_1) + resolve_mr(project, iid: iid_2)
+ context 'no arguments' do
+ it 'returns all merge requests' do
+ result = resolve_mr(project, {})
+
+ expect(result).to contain_exactly(merge_request_1, merge_request_2, merge_request_3, merge_request_4, merge_request_5, merge_request_6)
end
- expect(result).to contain_exactly(merge_request_1, merge_request_2)
+ it 'returns only merge requests that the current user can see' do
+ result = resolve_mr(project, {}, user: build(:user))
+
+ expect(result).to be_empty
+ end
end
- it 'batch-resolves by target project full path and IIDS' do
- result = batch_sync(max_queries: 2) do
- resolve_mr(project, iids: [iid_1, iid_2])
+ context 'by iid alone' do
+ it 'batch-resolves by target project full path and individual IID' do
+ result = batch_sync(max_queries: 2) do
+ [iid_1, iid_2].map { |iid| resolve_mr_single(project, iid) }
+ end
+
+ expect(result).to contain_exactly(merge_request_1, merge_request_2)
+ end
+
+ it 'batch-resolves by target project full path and IIDS' do
+ result = batch_sync(max_queries: 2) do
+ resolve_mr(project, iids: [iid_1, iid_2])
+ end
+
+ expect(result).to contain_exactly(merge_request_1, merge_request_2)
+ end
+
+ it 'can batch-resolve merge requests from different projects' do
+ result = batch_sync(max_queries: 3) do
+ resolve_mr(project, iids: iid_1) +
+ resolve_mr(project, iids: iid_2) +
+ resolve_mr(other_project, iids: other_iid)
+ end
+
+ expect(result).to contain_exactly(merge_request_1, merge_request_2, other_merge_request)
+ end
+
+ it 'resolves an unknown iid to be empty' do
+ result = batch_sync { resolve_mr_single(project, -1) }
+
+ expect(result).to be_nil
end
- expect(result).to contain_exactly(merge_request_1, merge_request_2)
+ it 'resolves empty iids to be empty' do
+ result = batch_sync { resolve_mr(project, iids: []) }
+
+ expect(result).to be_empty
+ end
+
+ it 'resolves an unknown project to be nil when single' do
+ result = batch_sync { resolve_mr_single(nil, iid_1) }
+
+ expect(result).to be_nil
+ end
+
+ it 'resolves an unknown project to be empty' do
+ result = batch_sync { resolve_mr(nil, iids: [iid_1]) }
+
+ expect(result).to be_empty
+ end
end
- it 'can batch-resolve merge requests from different projects' do
- result = batch_sync(max_queries: 3) do
- resolve_mr(project, iid: iid_1) +
- resolve_mr(project, iid: iid_2) +
- resolve_mr(other_project, iid: other_iid)
+ context 'by source branches' do
+ it 'takes one argument' do
+ result = resolve_mr(project, source_branch: [merge_request_3.source_branch])
+
+ expect(result).to contain_exactly(merge_request_3)
end
- expect(result).to contain_exactly(merge_request_1, merge_request_2, other_merge_request)
+ it 'takes more than one argument' do
+ mrs = [merge_request_3, merge_request_4]
+ branches = mrs.map(&:source_branch)
+ result = resolve_mr(project, source_branch: branches )
+
+ expect(result).to match_array(mrs)
+ end
end
- it 'resolves an unknown iid to be empty' do
- result = batch_sync { resolve_mr(project, iid: -1) }
+ context 'by target branches' do
+ it 'takes one argument' do
+ result = resolve_mr(project, target_branch: [merge_request_3.target_branch])
+
+ expect(result).to contain_exactly(merge_request_3)
+ end
- expect(result.compact).to be_empty
+ it 'takes more than one argument' do
+ mrs = [merge_request_3, merge_request_4]
+ branches = mrs.map(&:target_branch)
+ result = resolve_mr(project, target_branch: branches )
+
+ expect(result.compact).to match_array(mrs)
+ end
end
- it 'resolves empty iids to be empty' do
- result = batch_sync { resolve_mr(project, iids: []) }
+ context 'by state' do
+ it 'takes one argument' do
+ result = resolve_mr(project, state: 'locked')
- expect(result).to be_empty
+ expect(result).to contain_exactly(merge_request_4, merge_request_5)
+ end
end
- it 'resolves an unknown project to be empty' do
- result = batch_sync { resolve_mr(nil, iid: iid_1) }
+ context 'by label' do
+ let_it_be(:label) { merge_request_6.labels.first }
+ let_it_be(:with_label) { create(:labeled_merge_request, :closed, labels: [label], **common_attrs) }
- expect(result.compact).to be_empty
+ it 'takes one argument' do
+ result = resolve_mr(project, label_name: [label.title])
+
+ expect(result).to contain_exactly(merge_request_6, with_label)
+ end
+
+ it 'takes multiple arguments, with semantics of ALL MUST MATCH' do
+ result = resolve_mr(project, label_name: merge_request_6.labels.map(&:title))
+
+ expect(result).to contain_exactly(merge_request_6)
+ end
+ end
+
+ describe 'combinations' do
+ it 'requires all filters' do
+ create(:merge_request, :closed, source_project: project, target_project: project, source_branch: merge_request_4.source_branch)
+
+ result = resolve_mr(project, source_branch: [merge_request_4.source_branch], state: 'locked')
+
+ expect(result.compact).to contain_exactly(merge_request_4)
+ end
end
end
- def resolve_mr(project, args)
- resolve(described_class, obj: project, args: args)
+ def resolve_mr_single(project, iid)
+ resolve_mr(project, { iids: iid }, resolver: described_class.single)
+ end
+
+ def resolve_mr(project, args, resolver: described_class, user: current_user)
+ resolve(resolver, obj: project, args: args, ctx: { current_user: user })
end
end
diff --git a/spec/graphql/resolvers/project_members_resolver_spec.rb b/spec/graphql/resolvers/project_members_resolver_spec.rb
new file mode 100644
index 00000000000..3209838850b
--- /dev/null
+++ b/spec/graphql/resolvers/project_members_resolver_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Resolvers::ProjectMembersResolver do
+ include GraphqlHelpers
+
+ context "with a group" do
+ let_it_be(:root_group) { create(:group) }
+ let_it_be(:group_1) { create(:group, parent: root_group) }
+ let_it_be(:group_2) { create(:group, parent: root_group) }
+ let_it_be(:project) { create(:project, :public, group: group_1) }
+
+ let_it_be(:user_1) { create(:user, name: 'test user') }
+ let_it_be(:user_2) { create(:user, name: 'test user 2') }
+ let_it_be(:user_3) { create(:user, name: 'another user 1') }
+ let_it_be(:user_4) { create(:user, name: 'another user 2') }
+
+ let_it_be(:project_member) { create(:project_member, user: user_1, project: project) }
+ let_it_be(:group_1_member) { create(:group_member, user: user_2, group: group_1) }
+ let_it_be(:group_2_member) { create(:group_member, user: user_3, group: group_2) }
+ let_it_be(:root_group_member) { create(:group_member, user: user_4, group: root_group) }
+
+ let(:args) { {} }
+
+ subject do
+ resolve(described_class, obj: project, args: args, ctx: { context: user_4 })
+ end
+
+ describe '#resolve' do
+ it 'finds all project members' do
+ expect(subject).to contain_exactly(project_member, group_1_member, root_group_member)
+ end
+
+ context 'with search' do
+ context 'when the search term matches a user' do
+ let(:args) { { search: 'test' } }
+
+ it 'searches users by user name' do
+ expect(subject).to contain_exactly(project_member, group_1_member)
+ end
+ end
+
+ context 'when the search term does not match any user' do
+ let(:args) { { search: 'nothing' } }
+
+ it 'is empty' do
+ expect(subject).to be_empty
+ end
+ end
+ end
+
+ context 'when project is nil' do
+ let(:project) { nil }
+
+ it 'returns nil' do
+ expect(subject).to be_empty
+ end
+ end
+ end
+ end
+end
diff --git a/spec/graphql/resolvers/project_pipeline_resolver_spec.rb b/spec/graphql/resolvers/project_pipeline_resolver_spec.rb
new file mode 100644
index 00000000000..72049f16d7d
--- /dev/null
+++ b/spec/graphql/resolvers/project_pipeline_resolver_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+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(:other_pipeline) { create(:ci_pipeline) }
+ let(:current_user) { create(:user) }
+
+ def resolve_pipeline(project, args)
+ resolve(described_class, obj: project, args: args, ctx: { current_user: current_user })
+ end
+
+ it 'resolves pipeline for the passed iid' do
+ result = batch_sync do
+ resolve_pipeline(project, { iid: '1234' })
+ end
+
+ expect(result).to eq(pipeline)
+ end
+
+ it 'does not resolve a pipeline outside the project' do
+ result = batch_sync do
+ resolve_pipeline(other_pipeline.project, { iid: '1234' })
+ end
+
+ expect(result).to be_nil
+ end
+
+ it 'errors when no iid is passed' do
+ expect { resolve_pipeline(project, {}) }.to raise_error(ArgumentError)
+ end
+end
diff --git a/spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb b/spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb
index 7146bfb441b..9811075a613 100644
--- a/spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb
+++ b/spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb
@@ -40,16 +40,6 @@ describe Resolvers::Projects::JiraImportsResolver do
let_it_be(:jira_import1) { create(:jira_import_state, :finished, project: project, jira_project_key: 'AA', created_at: 2.days.ago) }
let_it_be(:jira_import2) { create(:jira_import_state, :finished, project: project, jira_project_key: 'BB', created_at: 5.days.ago) }
- context 'when feature flag disabled' do
- let(:current_user) { user }
-
- before do
- stub_feature_flags(jira_issue_import: false)
- end
-
- it_behaves_like 'no Jira import access'
- end
-
context 'when user cannot read Jira imports' do
context 'when anonymous user' do
let(:current_user) { nil }
diff --git a/spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb b/spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb
new file mode 100644
index 00000000000..364e2aa6ca8
--- /dev/null
+++ b/spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Resolvers::Projects::JiraProjectsResolver do
+ include GraphqlHelpers
+
+ describe '#resolve' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+
+ shared_examples 'no project service access' do
+ it 'raises error' do
+ expect do
+ resolve_jira_projects
+ end.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+
+ context 'when project has no jira service' do
+ let_it_be(:jira_service) { nil }
+
+ context 'when user is a maintainer' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ it_behaves_like 'no project service access'
+ end
+ end
+
+ context 'when project has jira service' do
+ let(:jira_service) { create(:jira_service, project: project) }
+
+ context 'when user is a developer' do
+ before do
+ project.add_developer(user)
+ end
+
+ it_behaves_like 'no project service access'
+ end
+
+ context 'when user is a maintainer' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ context 'when Jira connection is valid' do
+ include_context 'jira projects request context'
+
+ it 'returns jira projects' do
+ jira_projects = resolve_jira_projects
+ project_keys = jira_projects.map(&:key)
+ project_names = jira_projects.map(&:name)
+ project_ids = jira_projects.map(&:id)
+
+ expect(jira_projects.size).to eq 2
+ expect(project_keys).to eq(%w(EX ABC))
+ expect(project_names).to eq(%w(Example Alphabetical))
+ expect(project_ids).to eq(%w(10000 10001))
+ end
+ end
+
+ context 'when Jira connection is not valid' do
+ before do
+ WebMock.stub_request(:get, 'https://jira.example.com/rest/api/2/project/search?maxResults=50&query=&startAt=0')
+ .to_raise(JIRA::HTTPError.new(double(message: 'Some failure.')))
+ end
+
+ it 'raises failure error' do
+ expect { resolve_jira_projects }.to raise_error('Jira request error: Some failure.')
+ end
+ end
+ end
+ end
+ end
+
+ def resolve_jira_projects(args = {}, context = { current_user: user })
+ resolve(described_class, obj: jira_service, args: args, ctx: context)
+ end
+end
diff --git a/spec/graphql/resolvers/user_resolver_spec.rb b/spec/graphql/resolvers/user_resolver_spec.rb
new file mode 100644
index 00000000000..45a8816bf26
--- /dev/null
+++ b/spec/graphql/resolvers/user_resolver_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Resolvers::UserResolver do
+ include GraphqlHelpers
+
+ describe '#resolve' do
+ let_it_be(:user) { create(:user) }
+
+ context 'when neither an ID or a username is provided' do
+ it 'raises an ArgumentError' do
+ expect { resolve_user }
+ .to raise_error(Gitlab::Graphql::Errors::ArgumentError)
+ end
+ end
+
+ it 'raises an ArgumentError when both an ID and username are provided' do
+ expect { resolve_user(id: user.to_global_id, username: user.username) }
+ .to raise_error(Gitlab::Graphql::Errors::ArgumentError)
+ end
+
+ context 'by username' do
+ it 'returns the correct user' do
+ expect(
+ resolve_user(username: user.username)
+ ).to eq(user)
+ end
+ end
+
+ context 'by ID' do
+ it 'returns the correct user' do
+ expect(
+ resolve_user(id: user.to_global_id)
+ ).to eq(user)
+ end
+ end
+ end
+
+ private
+
+ def resolve_user(args = {})
+ sync(resolve(described_class, args: args))
+ end
+end
diff --git a/spec/graphql/resolvers/users_resolver_spec.rb b/spec/graphql/resolvers/users_resolver_spec.rb
new file mode 100644
index 00000000000..e752500d52f
--- /dev/null
+++ b/spec/graphql/resolvers/users_resolver_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Resolvers::UsersResolver do
+ include GraphqlHelpers
+
+ let_it_be(:user1) { create(:user) }
+ let_it_be(:user2) { create(:user) }
+
+ describe '#resolve' do
+ it 'raises an error when read_users_list is not authorized' do
+ expect(Ability).to receive(:allowed?).with(nil, :read_users_list).and_return(false)
+
+ expect { resolve_users }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+
+ context 'when no arguments are passed' do
+ it 'returns all users' do
+ expect(resolve_users).to contain_exactly(user1, user2)
+ end
+ end
+
+ context 'when both ids and usernames are passed ' do
+ it 'raises an error' do
+ expect { resolve_users(ids: [user1.to_global_id.to_s], usernames: [user1.username]) }
+ .to raise_error(Gitlab::Graphql::Errors::ArgumentError)
+ end
+ end
+
+ context 'when a set of IDs is passed' do
+ it 'returns those users' do
+ expect(
+ resolve_users(ids: [user1.to_global_id.to_s, user2.to_global_id.to_s])
+ ).to contain_exactly(user1, user2)
+ end
+ end
+
+ context 'when a set of usernames is passed' do
+ it 'returns those users' do
+ expect(
+ resolve_users(usernames: [user1.username, user2.username])
+ ).to contain_exactly(user1, user2)
+ end
+ end
+ end
+
+ def resolve_users(args = {})
+ resolve(described_class, args: args)
+ end
+end
diff --git a/spec/graphql/types/access_level_enum_spec.rb b/spec/graphql/types/access_level_enum_spec.rb
new file mode 100644
index 00000000000..05a6d6d5545
--- /dev/null
+++ b/spec/graphql/types/access_level_enum_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+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])
+ end
+end
diff --git a/spec/graphql/types/access_level_type_spec.rb b/spec/graphql/types/access_level_type_spec.rb
new file mode 100644
index 00000000000..b9711a9aa4b
--- /dev/null
+++ b/spec/graphql/types/access_level_type_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe GitlabSchema.types['AccessLevel'] do
+ specify { expect(described_class.graphql_name).to eq('AccessLevel') }
+ specify { expect(described_class).to require_graphql_authorizations(nil) }
+
+ it 'has expected fields' do
+ expected_fields = [:integer_value, :string_value]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ 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 9c326f30e3c..5acbf8ebb7a 100644
--- a/spec/graphql/types/alert_management/alert_type_spec.rb
+++ b/spec/graphql/types/alert_management/alert_type_spec.rb
@@ -24,6 +24,9 @@ describe GitlabSchema.types['AlertManagementAlert'] do
details
created_at
updated_at
+ assignees
+ notes
+ discussions
]
expect(described_class).to have_graphql_fields(*expected_fields)
diff --git a/spec/graphql/types/base_field_spec.rb b/spec/graphql/types/base_field_spec.rb
index daed5725e26..3ec33c75803 100644
--- a/spec/graphql/types/base_field_spec.rb
+++ b/spec/graphql/types/base_field_spec.rb
@@ -135,25 +135,6 @@ describe Types::BaseField do
it 'returns true if the feature is enabled' do
expect(field.visible?(context)).to eq(true)
end
-
- context 'falsey feature_flag values' do
- using RSpec::Parameterized::TableSyntax
-
- where(:flag, :feature_value, :visible) do
- '' | false | true
- '' | true | true
- nil | false | true
- nil | true | true
- end
-
- with_them do
- it 'returns the correct value' do
- stub_feature_flags(flag => feature_value)
-
- expect(field.visible?(context)).to eq(visible)
- end
- end
- end
end
end
end
diff --git a/spec/graphql/types/commit_action_mode_enum_spec.rb b/spec/graphql/types/commit_action_mode_enum_spec.rb
new file mode 100644
index 00000000000..9e1a27ea254
--- /dev/null
+++ b/spec/graphql/types/commit_action_mode_enum_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['CommitActionMode'] do
+ it { expect(described_class.graphql_name).to eq('CommitActionMode') }
+
+ it 'exposes all the existing commit actions' do
+ expect(described_class.values.keys).to match_array(%w[CREATE UPDATE MOVE DELETE CHMOD])
+ end
+end
diff --git a/spec/graphql/types/commit_encoding_enum_spec.rb b/spec/graphql/types/commit_encoding_enum_spec.rb
new file mode 100644
index 00000000000..30686a0c712
--- /dev/null
+++ b/spec/graphql/types/commit_encoding_enum_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['CommitEncoding'] do
+ it { expect(described_class.graphql_name).to eq('CommitEncoding') }
+
+ it 'exposes all the existing encoding option' do
+ expect(described_class.values.keys).to match_array(%w[TEXT BASE64])
+ end
+end
diff --git a/spec/graphql/types/container_expiration_policy_cadence_enum_spec.rb b/spec/graphql/types/container_expiration_policy_cadence_enum_spec.rb
new file mode 100644
index 00000000000..08c777cd365
--- /dev/null
+++ b/spec/graphql/types/container_expiration_policy_cadence_enum_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['ContainerExpirationPolicyCadenceEnum'] do
+ let_it_be(:expected_values) { %w[EVERY_DAY EVERY_WEEK EVERY_TWO_WEEKS EVERY_MONTH EVERY_THREE_MONTHS] }
+
+ it_behaves_like 'exposing container expiration policy option', :cadence
+end
diff --git a/spec/graphql/types/container_expiration_policy_keep_enum_spec.rb b/spec/graphql/types/container_expiration_policy_keep_enum_spec.rb
new file mode 100644
index 00000000000..1a5b4bdd3bb
--- /dev/null
+++ b/spec/graphql/types/container_expiration_policy_keep_enum_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['ContainerExpirationPolicyKeepEnum'] do
+ let_it_be(:expected_values) { %w[ONE_TAG FIVE_TAGS TEN_TAGS TWENTY_FIVE_TAGS FIFTY_TAGS ONE_HUNDRED_TAGS] }
+
+ it_behaves_like 'exposing container expiration policy option', :keep_n
+end
diff --git a/spec/graphql/types/container_expiration_policy_older_than_enum_spec.rb b/spec/graphql/types/container_expiration_policy_older_than_enum_spec.rb
new file mode 100644
index 00000000000..47f0ca22522
--- /dev/null
+++ b/spec/graphql/types/container_expiration_policy_older_than_enum_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['ContainerExpirationPolicyOlderThanEnum'] do
+ let_it_be(:expected_values) { %w[SEVEN_DAYS FOURTEEN_DAYS THIRTY_DAYS NINETY_DAYS] }
+
+ it_behaves_like 'exposing container expiration policy option', :older_than
+end
diff --git a/spec/graphql/types/container_expiration_policy_type_spec.rb b/spec/graphql/types/container_expiration_policy_type_spec.rb
new file mode 100644
index 00000000000..8924ab67847
--- /dev/null
+++ b/spec/graphql/types/container_expiration_policy_type_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['ContainerExpirationPolicy'] do
+ specify { expect(described_class.graphql_name).to eq('ContainerExpirationPolicy') }
+
+ specify { expect(described_class.description).to eq('A tag expiration policy designed to keep only the images that matter most') }
+
+ specify { expect(described_class).to require_graphql_authorizations(:destroy_container_image) }
+
+ describe 'older_than field' do
+ subject { described_class.fields['olderThan'] }
+
+ it 'returns older_than enum' do
+ is_expected.to have_graphql_type(Types::ContainerExpirationPolicyOlderThanEnum)
+ end
+ end
+
+ describe 'keep n field' do
+ subject { described_class.fields['keepN'] }
+
+ it 'returns keep enum' do
+ is_expected.to have_graphql_type(Types::ContainerExpirationPolicyKeepEnum)
+ end
+ end
+end
diff --git a/spec/graphql/types/evidence_type_spec.rb b/spec/graphql/types/evidence_type_spec.rb
new file mode 100644
index 00000000000..4a11f7bcda9
--- /dev/null
+++ b/spec/graphql/types/evidence_type_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['ReleaseEvidence'] do
+ it { expect(described_class).to require_graphql_authorizations(:download_code) }
+
+ it 'has the expected fields' do
+ expected_fields = %w[
+ id sha filepath collected_at
+ ]
+
+ expect(described_class).to include_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/group_member_type_spec.rb b/spec/graphql/types/group_member_type_spec.rb
new file mode 100644
index 00000000000..5d09e60d21c
--- /dev/null
+++ b/spec/graphql/types/group_member_type_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Types::GroupMemberType do
+ specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Group) }
+
+ specify { expect(described_class.graphql_name).to eq('GroupMember') }
+
+ specify { expect(described_class).to require_graphql_authorizations(:read_group) }
+
+ it 'has the expected fields' do
+ expected_fields = %w[
+ access_level created_by created_at updated_at expires_at group
+ ]
+
+ expect(described_class).to include_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/group_type_spec.rb b/spec/graphql/types/group_type_spec.rb
index a834a9038db..c56cd40ef12 100644
--- a/spec/graphql/types/group_type_spec.rb
+++ b/spec/graphql/types/group_type_spec.rb
@@ -29,4 +29,6 @@ describe GitlabSchema.types['Group'] do
is_expected.to have_graphql_type(Types::BoardType.connection_type)
end
end
+
+ it_behaves_like 'a GraphQL type with labels'
end
diff --git a/spec/graphql/types/jira_import_type_spec.rb b/spec/graphql/types/jira_import_type_spec.rb
index ac1aa672e30..fa1152aec41 100644
--- a/spec/graphql/types/jira_import_type_spec.rb
+++ b/spec/graphql/types/jira_import_type_spec.rb
@@ -6,6 +6,9 @@ describe GitlabSchema.types['JiraImport'] do
specify { expect(described_class.graphql_name).to eq('JiraImport') }
it 'has the expected fields' do
- expect(described_class).to have_graphql_fields(:jira_project_key, :createdAt, :scheduled_at, :scheduled_by)
+ expect(described_class).to have_graphql_fields(
+ :jira_project_key, :created_at, :scheduled_at, :scheduled_by,
+ :failed_to_import_count, :imported_issues_count, :total_issue_count
+ )
end
end
diff --git a/spec/graphql/types/merge_request_type_spec.rb b/spec/graphql/types/merge_request_type_spec.rb
index e7ab2100084..0f48264c99f 100644
--- a/spec/graphql/types/merge_request_type_spec.rb
+++ b/spec/graphql/types/merge_request_type_spec.rb
@@ -19,10 +19,11 @@ describe GitlabSchema.types['MergeRequest'] do
force_remove_source_branch merge_status in_progress_merge_commit_sha
merge_error allow_collaboration should_be_rebased rebase_commit_sha
rebase_in_progress merge_commit_message default_merge_commit_message
- merge_ongoing source_branch_exists mergeable_discussions_state web_url
+ merge_ongoing mergeable_discussions_state web_url
+ source_branch_exists target_branch_exists
upvotes downvotes head_pipeline pipelines task_completion_status
milestone assignees participants subscribed labels discussion_locked time_estimate
- total_time_spent reference
+ total_time_spent reference author merged_at
]
expect(described_class).to have_graphql_fields(*expected_fields)
diff --git a/spec/graphql/types/metrics/dashboard_type_spec.rb b/spec/graphql/types/metrics/dashboard_type_spec.rb
index 81219c596a7..0dbd0d8b38d 100644
--- a/spec/graphql/types/metrics/dashboard_type_spec.rb
+++ b/spec/graphql/types/metrics/dashboard_type_spec.rb
@@ -7,7 +7,7 @@ describe GitlabSchema.types['MetricsDashboard'] do
it 'has the expected fields' do
expected_fields = %w[
- path annotations
+ path annotations schema_validation_warnings
]
expect(described_class).to have_graphql_fields(*expected_fields)
diff --git a/spec/graphql/types/notes/diff_position_type_spec.rb b/spec/graphql/types/notes/diff_position_type_spec.rb
index 01f355cb278..87f3810d55c 100644
--- a/spec/graphql/types/notes/diff_position_type_spec.rb
+++ b/spec/graphql/types/notes/diff_position_type_spec.rb
@@ -1,11 +1,22 @@
# frozen_string_literal: true
+
require 'spec_helper'
describe GitlabSchema.types['DiffPosition'] do
it 'exposes the expected fields' do
- expected_fields = [:diff_refs, :file_path, :old_path,
- :new_path, :position_type, :old_line, :new_line, :x, :y,
- :width, :height]
+ expected_fields = %i[
+ diff_refs
+ file_path
+ height
+ new_line
+ new_path
+ old_line
+ old_path
+ position_type
+ width
+ x
+ y
+ ]
expect(described_class).to have_graphql_fields(*expected_fields)
end
diff --git a/spec/graphql/types/notes/discussion_type_spec.rb b/spec/graphql/types/notes/discussion_type_spec.rb
index 44774594d17..177000b01b2 100644
--- a/spec/graphql/types/notes/discussion_type_spec.rb
+++ b/spec/graphql/types/notes/discussion_type_spec.rb
@@ -1,8 +1,22 @@
# frozen_string_literal: true
+
require 'spec_helper'
describe GitlabSchema.types['Discussion'] do
- specify { expect(described_class).to have_graphql_fields(:id, :created_at, :notes, :reply_id) }
+ it 'exposes the expected fields' do
+ expected_fields = %i[
+ created_at
+ id
+ notes
+ reply_id
+ resolvable
+ resolved
+ resolved_at
+ resolved_by
+ ]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
specify { expect(described_class).to require_graphql_authorizations(:read_note) }
end
diff --git a/spec/graphql/types/notes/note_type_spec.rb b/spec/graphql/types/notes/note_type_spec.rb
index 019f742ee77..d6cd0800234 100644
--- a/spec/graphql/types/notes/note_type_spec.rb
+++ b/spec/graphql/types/notes/note_type_spec.rb
@@ -1,11 +1,27 @@
# frozen_string_literal: true
+
require 'spec_helper'
describe GitlabSchema.types['Note'] do
it 'exposes the expected fields' do
- expected_fields = [:id, :project, :author, :body, :created_at,
- :updated_at, :discussion, :resolvable, :position, :user_permissions,
- :resolved_by, :resolved_at, :system, :body_html, :confidential]
+ expected_fields = %i[
+ author
+ body
+ body_html
+ confidential
+ created_at
+ discussion
+ id
+ position
+ project
+ resolvable
+ resolved
+ resolved_at
+ resolved_by
+ system
+ updated_at
+ user_permissions
+ ]
expect(described_class).to have_graphql_fields(*expected_fields)
end
diff --git a/spec/graphql/types/notes/noteable_type_spec.rb b/spec/graphql/types/notes/noteable_type_spec.rb
index 4a81f45bd4e..88d8eae56d1 100644
--- a/spec/graphql/types/notes/noteable_type_spec.rb
+++ b/spec/graphql/types/notes/noteable_type_spec.rb
@@ -1,14 +1,23 @@
# frozen_string_literal: true
+
require 'spec_helper'
describe Types::Notes::NoteableType do
- specify { expect(described_class).to have_graphql_fields(:notes, :discussions) }
+ it 'exposes the expected fields' do
+ expected_fields = %i[
+ discussions
+ notes
+ ]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
describe ".resolve_type" do
it 'knows the correct type for objects' do
expect(described_class.resolve_type(build(:issue), {})).to eq(Types::IssueType)
expect(described_class.resolve_type(build(:merge_request), {})).to eq(Types::MergeRequestType)
expect(described_class.resolve_type(build(:design), {})).to eq(Types::DesignManagement::DesignType)
+ expect(described_class.resolve_type(build(:alert_management_alert), {})).to eq(Types::AlertManagement::AlertType)
end
end
end
diff --git a/spec/graphql/types/project_member_type_spec.rb b/spec/graphql/types/project_member_type_spec.rb
new file mode 100644
index 00000000000..1b1f6c24a32
--- /dev/null
+++ b/spec/graphql/types/project_member_type_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Types::ProjectMemberType do
+ specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Project) }
+
+ specify { expect(described_class.graphql_name).to eq('ProjectMember') }
+
+ specify { expect(described_class).to require_graphql_authorizations(:read_project) }
+
+ it 'has the expected fields' do
+ expected_fields = %w[
+ access_level created_by created_at updated_at expires_at project user
+ ]
+
+ expect(described_class).to include_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb
index 6368f743720..8ee9aa9cf3a 100644
--- a/spec/graphql/types/project_type_spec.rb
+++ b/spec/graphql/types/project_type_spec.rb
@@ -26,6 +26,7 @@ describe GitlabSchema.types['Project'] do
grafanaIntegration autocloseReferencedIssues suggestion_commit_message environments
boards jira_import_status jira_imports services releases release
alert_management_alerts alert_management_alert alert_management_alert_status_counts
+ container_expiration_policy
]
expect(described_class).to include_graphql_fields(*expected_fields)
@@ -45,18 +46,32 @@ describe GitlabSchema.types['Project'] do
it { is_expected.to have_graphql_resolver(Resolvers::IssuesResolver) }
end
- describe 'merge_requests field' do
+ describe 'merge_request field' do
subject { described_class.fields['mergeRequest'] }
it { is_expected.to have_graphql_type(Types::MergeRequestType) }
it { is_expected.to have_graphql_resolver(Resolvers::MergeRequestsResolver.single) }
+ it { is_expected.to have_graphql_arguments(:iid) }
end
- describe 'merge_request field' do
+ describe 'merge_requests field' do
subject { described_class.fields['mergeRequests'] }
it { is_expected.to have_graphql_type(Types::MergeRequestType.connection_type) }
it { is_expected.to have_graphql_resolver(Resolvers::MergeRequestsResolver) }
+
+ it do
+ is_expected.to have_graphql_arguments(:iids,
+ :source_branches,
+ :target_branches,
+ :state,
+ :labels,
+ :before,
+ :after,
+ :first,
+ :last
+ )
+ end
end
describe 'snippets field' do
@@ -80,6 +95,13 @@ describe GitlabSchema.types['Project'] do
it { is_expected.to have_graphql_resolver(Resolvers::EnvironmentsResolver) }
end
+ describe 'members field' do
+ subject { described_class.fields['projectMembers'] }
+
+ it { is_expected.to have_graphql_type(Types::ProjectMemberType.connection_type) }
+ it { is_expected.to have_graphql_resolver(Resolvers::ProjectMembersResolver) }
+ end
+
describe 'boards field' do
subject { described_class.fields['boards'] }
@@ -111,4 +133,12 @@ describe GitlabSchema.types['Project'] do
it { is_expected.to have_graphql_type(Types::ReleaseType.connection_type) }
it { is_expected.to have_graphql_resolver(Resolvers::ReleasesResolver) }
end
+
+ describe 'container expiration policy field' do
+ subject { described_class.fields['containerExpirationPolicy'] }
+
+ it { is_expected.to have_graphql_type(Types::ContainerExpirationPolicyType) }
+ end
+
+ it_behaves_like 'a GraphQL type with labels'
end
diff --git a/spec/graphql/types/projects/jira_project_type_spec.rb b/spec/graphql/types/projects/jira_project_type_spec.rb
new file mode 100644
index 00000000000..cbb01117717
--- /dev/null
+++ b/spec/graphql/types/projects/jira_project_type_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['JiraProject'] do
+ it { expect(described_class.graphql_name).to eq('JiraProject') }
+
+ it 'has basic expected fields' do
+ expect(described_class).to have_graphql_fields(:key, :project_id, :name)
+ end
+end
diff --git a/spec/graphql/types/projects/jira_service_type_spec.rb b/spec/graphql/types/projects/jira_service_type_spec.rb
index 91d7e4586cb..fad0c91caab 100644
--- a/spec/graphql/types/projects/jira_service_type_spec.rb
+++ b/spec/graphql/types/projects/jira_service_type_spec.rb
@@ -6,7 +6,7 @@ describe GitlabSchema.types['JiraService'] do
specify { expect(described_class.graphql_name).to eq('JiraService') }
it 'has basic expected fields' do
- expect(described_class).to have_graphql_fields(:type, :active)
+ expect(described_class).to have_graphql_fields(:type, :active, :projects)
end
specify { expect(described_class).to require_graphql_authorizations(:admin_project) }
diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb
index 1f269a80d00..1194391c26a 100644
--- a/spec/graphql/types/query_type_spec.rb
+++ b/spec/graphql/types/query_type_spec.rb
@@ -8,7 +8,18 @@ describe GitlabSchema.types['Query'] do
end
it 'has the expected fields' do
- expected_fields = %i[project namespace group echo metadata current_user snippets design_management]
+ expected_fields = %i[
+ project
+ namespace
+ group
+ echo
+ metadata
+ current_user
+ snippets
+ design_management
+ user
+ users
+ ]
expect(described_class).to have_graphql_fields(*expected_fields).at_least
end
diff --git a/spec/graphql/types/release_assets_type_spec.rb b/spec/graphql/types/release_assets_type_spec.rb
new file mode 100644
index 00000000000..58f0f7ee697
--- /dev/null
+++ b/spec/graphql/types/release_assets_type_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['ReleaseAssets'] do
+ it { expect(described_class).to require_graphql_authorizations(:read_release) }
+
+ it 'has the expected fields' do
+ expected_fields = %w[
+ assets_count links sources
+ ]
+
+ expect(described_class).to include_graphql_fields(*expected_fields)
+ end
+
+ describe 'links field' do
+ subject { described_class.fields['links'] }
+
+ it { is_expected.to have_graphql_type(Types::ReleaseLinkType.connection_type) }
+ end
+
+ describe 'sources field' do
+ subject { described_class.fields['sources'] }
+
+ it { is_expected.to have_graphql_type(Types::ReleaseSourceType.connection_type) }
+ end
+end
diff --git a/spec/graphql/types/release_links_type_spec.rb b/spec/graphql/types/release_links_type_spec.rb
new file mode 100644
index 00000000000..49e04e120f4
--- /dev/null
+++ b/spec/graphql/types/release_links_type_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['ReleaseLink'] do
+ it { expect(described_class).to require_graphql_authorizations(:read_release) }
+
+ it 'has the expected fields' do
+ expected_fields = %w[
+ id name url external link_type
+ ]
+
+ expect(described_class).to include_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/release_source_type_spec.rb b/spec/graphql/types/release_source_type_spec.rb
new file mode 100644
index 00000000000..e471ac1a5ac
--- /dev/null
+++ b/spec/graphql/types/release_source_type_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['ReleaseSource'] do
+ it { expect(described_class).to require_graphql_authorizations(:read_release_sources) }
+
+ it 'has the expected fields' do
+ expected_fields = %w[
+ format url
+ ]
+
+ expect(described_class).to include_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/release_type_spec.rb b/spec/graphql/types/release_type_spec.rb
index d22a0b4f0fa..feafe5ed519 100644
--- a/spec/graphql/types/release_type_spec.rb
+++ b/spec/graphql/types/release_type_spec.rb
@@ -9,19 +9,31 @@ describe GitlabSchema.types['Release'] do
expected_fields = %w[
tag_name tag_path
description description_html
- name milestones author commit
+ name assets milestones evidences author commit
created_at released_at
]
expect(described_class).to include_graphql_fields(*expected_fields)
end
+ describe 'assets field' do
+ subject { described_class.fields['assets'] }
+
+ it { is_expected.to have_graphql_type(Types::ReleaseAssetsType) }
+ end
+
describe 'milestones field' do
subject { described_class.fields['milestones'] }
it { is_expected.to have_graphql_type(Types::MilestoneType.connection_type) }
end
+ describe 'evidences field' do
+ subject { described_class.fields['evidences'] }
+
+ it { is_expected.to have_graphql_type(Types::EvidenceType.connection_type) }
+ end
+
describe 'author field' do
subject { described_class.fields['author'] }
diff --git a/spec/graphql/types/resolvable_interface_spec.rb b/spec/graphql/types/resolvable_interface_spec.rb
new file mode 100644
index 00000000000..231287f9969
--- /dev/null
+++ b/spec/graphql/types/resolvable_interface_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Types::ResolvableInterface do
+ it 'exposes the expected fields' do
+ expected_fields = %i[
+ resolvable
+ resolved
+ resolved_at
+ resolved_by
+ ]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/snippet_type_spec.rb b/spec/graphql/types/snippet_type_spec.rb
index adc13d4d651..f24419ce9cc 100644
--- a/spec/graphql/types/snippet_type_spec.rb
+++ b/spec/graphql/types/snippet_type_spec.rb
@@ -11,11 +11,49 @@ describe GitlabSchema.types['Snippet'] do
:visibility_level, :created_at, :updated_at,
:web_url, :raw_url, :ssh_url_to_repo, :http_url_to_repo,
:notes, :discussions, :user_permissions,
- :description_html, :blob]
+ :description_html, :blob, :blobs]
expect(described_class).to have_graphql_fields(*expected_fields)
end
+ context 'when restricted visibility level is set to public' do
+ let_it_be(:snippet) { create(:personal_snippet, :repository, :public, author: user) }
+
+ let(:current_user) { user }
+ let(:query) do
+ %(
+ {
+ snippets {
+ nodes {
+ author {
+ id
+ }
+ }
+ }
+ }
+ )
+ end
+ let(:response) { subject.dig('data', 'snippets', 'nodes')[0] }
+
+ subject { GitlabSchema.execute(query, context: { current_user: current_user }).as_json }
+
+ before do
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
+ end
+
+ it 'returns snippet author' do
+ expect(response['author']).to be_present
+ end
+
+ context 'when user is not logged in' do
+ let(:current_user) { nil }
+
+ it 'returns snippet author as nil' do
+ expect(response['author']).to be_nil
+ end
+ end
+ end
+
describe 'authorizations' do
specify { expect(described_class).to require_graphql_authorizations(:read_snippet) }
end
@@ -76,30 +114,14 @@ describe GitlabSchema.types['Snippet'] do
describe '#blob' do
let(:query_blob) { subject.dig('data', 'snippets', 'edges')[0]['node']['blob'] }
- let(:query) do
- %(
- {
- snippets {
- edges {
- node {
- blob {
- name
- path
- }
- }
- }
- }
- }
- )
- end
- subject { GitlabSchema.execute(query, context: { current_user: user }).as_json }
+ subject { GitlabSchema.execute(snippet_query_for(field: 'blob'), context: { current_user: user }).as_json }
context 'when snippet has repository' do
let!(:snippet) { create(:personal_snippet, :repository, :public, author: user) }
let(:blob) { snippet.blobs.first }
- it 'returns blob from the repository' do
+ it 'returns the first blob from the repository' do
expect(query_blob['name']).to eq blob.name
expect(query_blob['path']).to eq blob.path
end
@@ -115,4 +137,58 @@ describe GitlabSchema.types['Snippet'] do
end
end
end
+
+ describe '#blobs' do
+ let_it_be(:snippet) { create(:personal_snippet, :public, author: user) }
+ let(:query_blobs) { subject.dig('data', 'snippets', 'edges')[0]['node']['blobs'] }
+
+ subject { GitlabSchema.execute(snippet_query_for(field: 'blobs'), context: { current_user: user }).as_json }
+
+ shared_examples 'an array' do
+ it 'returns an array of snippet blobs' do
+ expect(query_blobs).to be_an(Array)
+ end
+ end
+
+ context 'when snippet does not have a repository' do
+ let(:blob) { snippet.blob }
+
+ it_behaves_like 'an array'
+
+ it 'contains the first blob from the snippet' do
+ expect(query_blobs.first['name']).to eq blob.name
+ expect(query_blobs.first['path']).to eq blob.path
+ end
+ end
+
+ context 'when snippet has repository' do
+ let_it_be(:snippet) { create(:personal_snippet, :repository, :public, author: user) }
+ let(:blobs) { snippet.blobs }
+
+ it_behaves_like 'an array'
+
+ it 'contains all the blobs from the repository' do
+ resulting_blobs_names = query_blobs.map { |b| b['name'] }
+
+ expect(resulting_blobs_names).to match_array(blobs.map(&:name))
+ end
+ end
+ end
+
+ def snippet_query_for(field:)
+ %(
+ {
+ snippets {
+ edges {
+ node {
+ #{field} {
+ name
+ path
+ }
+ }
+ }
+ }
+ }
+ )
+ end
end
diff --git a/spec/graphql/types/snippets/file_input_action_enum_spec.rb b/spec/graphql/types/snippets/file_input_action_enum_spec.rb
new file mode 100644
index 00000000000..2ccc8b04b8f
--- /dev/null
+++ b/spec/graphql/types/snippets/file_input_action_enum_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Types::Snippets::FileInputActionEnum do
+ specify { expect(described_class.graphql_name).to eq('SnippetFileInputActionEnum') }
+
+ it 'exposes all file input action types' do
+ expect(described_class.values.keys).to eq(%w[create update delete move])
+ end
+end
diff --git a/spec/graphql/types/snippets/file_input_type_spec.rb b/spec/graphql/types/snippets/file_input_type_spec.rb
new file mode 100644
index 00000000000..62e5caf20b7
--- /dev/null
+++ b/spec/graphql/types/snippets/file_input_type_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Types::Snippets::FileInputType do
+ specify { expect(described_class.graphql_name).to eq('SnippetFileInputType') }
+
+ it 'has the correct arguments' do
+ expect(described_class.arguments.keys).to match_array(%w[filePath action previousPath content])
+ end
+
+ it 'sets the type of action argument to FileInputActionEnum' do
+ expect(described_class.arguments['action'].type.of_type).to eq(Types::Snippets::FileInputActionEnum)
+ end
+end
diff --git a/spec/graphql/types/user_type_spec.rb b/spec/graphql/types/user_type_spec.rb
index cf1e91afb80..7b34588b0ff 100644
--- a/spec/graphql/types/user_type_spec.rb
+++ b/spec/graphql/types/user_type_spec.rb
@@ -9,7 +9,19 @@ describe GitlabSchema.types['User'] do
it 'has the expected fields' do
expected_fields = %w[
- id user_permissions snippets name username avatarUrl webUrl todos state
+ id
+ user_permissions
+ snippets
+ name
+ username
+ avatarUrl
+ webUrl
+ todos
+ state
+ authoredMergeRequests
+ assignedMergeRequests
+ groupMemberships
+ projectMemberships
]
expect(described_class).to have_graphql_fields(*expected_fields)