summaryrefslogtreecommitdiff
path: root/spec/graphql
diff options
context:
space:
mode:
Diffstat (limited to 'spec/graphql')
-rw-r--r--spec/graphql/mutations/alert_management/http_integration/create_spec.rb57
-rw-r--r--spec/graphql/mutations/alert_management/http_integration/destroy_spec.rb58
-rw-r--r--spec/graphql/mutations/alert_management/http_integration/reset_token_spec.rb58
-rw-r--r--spec/graphql/mutations/alert_management/http_integration/update_spec.rb58
-rw-r--r--spec/graphql/mutations/alert_management/prometheus_integration/create_spec.rb72
-rw-r--r--spec/graphql/mutations/alert_management/prometheus_integration/reset_token_spec.rb58
-rw-r--r--spec/graphql/mutations/alert_management/prometheus_integration/update_spec.rb58
-rw-r--r--spec/graphql/mutations/alert_management/update_alert_status_spec.rb4
-rw-r--r--spec/graphql/mutations/commits/create_spec.rb29
-rw-r--r--spec/graphql/mutations/container_expiration_policies/update_spec.rb20
-rw-r--r--spec/graphql/mutations/container_repositories/destroy_spec.rb67
-rw-r--r--spec/graphql/mutations/issues/set_assignees_spec.rb14
-rw-r--r--spec/graphql/mutations/issues/set_confidential_spec.rb4
-rw-r--r--spec/graphql/mutations/issues/set_due_date_spec.rb4
-rw-r--r--spec/graphql/mutations/issues/set_locked_spec.rb4
-rw-r--r--spec/graphql/mutations/issues/set_severity_spec.rb6
-rw-r--r--spec/graphql/mutations/issues/update_spec.rb6
-rw-r--r--spec/graphql/mutations/labels/create_spec.rb80
-rw-r--r--spec/graphql/mutations/merge_requests/set_assignees_spec.rb14
-rw-r--r--spec/graphql/mutations/merge_requests/set_labels_spec.rb4
-rw-r--r--spec/graphql/mutations/merge_requests/set_locked_spec.rb4
-rw-r--r--spec/graphql/mutations/merge_requests/set_milestone_spec.rb2
-rw-r--r--spec/graphql/mutations/merge_requests/set_wip_spec.rb4
-rw-r--r--spec/graphql/mutations/merge_requests/update_spec.rb4
-rw-r--r--spec/graphql/mutations/notes/reposition_image_diff_note_spec.rb60
-rw-r--r--spec/graphql/mutations/releases/create_spec.rb133
-rw-r--r--spec/graphql/mutations/terraform/state/delete_spec.rb55
-rw-r--r--spec/graphql/mutations/terraform/state/lock_spec.rb68
-rw-r--r--spec/graphql/mutations/terraform/state/unlock_spec.rb61
-rw-r--r--spec/graphql/mutations/todos/create_spec.rb44
-rw-r--r--spec/graphql/mutations/todos/mark_all_done_spec.rb2
-rw-r--r--spec/graphql/mutations/todos/restore_many_spec.rb19
-rw-r--r--spec/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver_spec.rb22
-rw-r--r--spec/graphql/resolvers/alert_management/integrations_resolver_spec.rb38
-rw-r--r--spec/graphql/resolvers/base_resolver_spec.rb184
-rw-r--r--spec/graphql/resolvers/ci/jobs_resolver_spec.rb40
-rw-r--r--spec/graphql/resolvers/ci/runner_setup_resolver_spec.rb104
-rw-r--r--spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb208
-rw-r--r--spec/graphql/resolvers/container_repositories_resolver_spec.rb58
-rw-r--r--spec/graphql/resolvers/design_management/design_resolver_spec.rb15
-rw-r--r--spec/graphql/resolvers/design_management/designs_resolver_spec.rb24
-rw-r--r--spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb7
-rw-r--r--spec/graphql/resolvers/echo_resolver_spec.rb4
-rw-r--r--spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb8
-rw-r--r--spec/graphql/resolvers/error_tracking/sentry_error_collection_resolver_spec.rb4
-rw-r--r--spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb4
-rw-r--r--spec/graphql/resolvers/group_members_resolver_spec.rb4
-rw-r--r--spec/graphql/resolvers/issues_resolver_spec.rb4
-rw-r--r--spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb1
-rw-r--r--spec/graphql/resolvers/merge_requests_resolver_spec.rb8
-rw-r--r--spec/graphql/resolvers/metadata_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/project_pipeline_resolver_spec.rb4
-rw-r--r--spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb4
-rw-r--r--spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb4
-rw-r--r--spec/graphql/resolvers/projects/services_resolver_spec.rb4
-rw-r--r--spec/graphql/resolvers/projects/snippets_resolver_spec.rb6
-rw-r--r--spec/graphql/resolvers/projects_resolver_spec.rb4
-rw-r--r--spec/graphql/resolvers/release_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/releases_resolver_spec.rb49
-rw-r--r--spec/graphql/resolvers/snippets/blobs_resolver_spec.rb4
-rw-r--r--spec/graphql/resolvers/snippets_resolver_spec.rb10
-rw-r--r--spec/graphql/resolvers/todo_resolver_spec.rb4
-rw-r--r--spec/graphql/resolvers/tree_resolver_spec.rb4
-rw-r--r--spec/graphql/resolvers/users/group_count_resolver_spec.rb62
-rw-r--r--spec/graphql/resolvers/users/snippets_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/users_resolver_spec.rb16
-rw-r--r--spec/graphql/types/admin/analytics/instance_statistics/measurement_identifier_enum_spec.rb5
-rw-r--r--spec/graphql/types/alert_management/http_integration_type_spec.rb9
-rw-r--r--spec/graphql/types/alert_management/integration_type_enum_spec.rb22
-rw-r--r--spec/graphql/types/alert_management/integration_type_spec.rb21
-rw-r--r--spec/graphql/types/alert_management/prometheus_integration_type_spec.rb60
-rw-r--r--spec/graphql/types/availability_enum_spec.rb11
-rw-r--r--spec/graphql/types/ci/detailed_status_type_spec.rb21
-rw-r--r--spec/graphql/types/ci/job_type_spec.rb1
-rw-r--r--spec/graphql/types/ci/runner_setup_type_spec.rb16
-rw-r--r--spec/graphql/types/commit_type_spec.rb2
-rw-r--r--spec/graphql/types/container_repository_cleanup_status_enum_spec.rb13
-rw-r--r--spec/graphql/types/container_repository_details_type_spec.rb23
-rw-r--r--spec/graphql/types/container_repository_status_enum_spec.rb9
-rw-r--r--spec/graphql/types/container_repository_tag_type_spec.rb15
-rw-r--r--spec/graphql/types/container_repository_type_spec.rb31
-rw-r--r--spec/graphql/types/countable_connection_type_spec.rb2
-rw-r--r--spec/graphql/types/custom_emoji_type_spec.rb11
-rw-r--r--spec/graphql/types/environment_type_spec.rb8
-rw-r--r--spec/graphql/types/global_id_type_spec.rb3
-rw-r--r--spec/graphql/types/grafana_integration_type_spec.rb1
-rw-r--r--spec/graphql/types/group_invitation_type_spec.rb19
-rw-r--r--spec/graphql/types/invitation_interface_spec.rb43
-rw-r--r--spec/graphql/types/issue_type_spec.rb8
-rw-r--r--spec/graphql/types/merge_request_type_spec.rb4
-rw-r--r--spec/graphql/types/permission_types/note_spec.rb4
-rw-r--r--spec/graphql/types/project_invitation_type_spec.rb19
-rw-r--r--spec/graphql/types/project_statistics_type_spec.rb2
-rw-r--r--spec/graphql/types/project_type_spec.rb3
-rw-r--r--spec/graphql/types/projects/services_enum_spec.rb2
-rw-r--r--spec/graphql/types/query_type_spec.rb14
-rw-r--r--spec/graphql/types/release_asset_link_input_type_spec.rb15
-rw-r--r--spec/graphql/types/release_assets_input_type_spec.rb15
-rw-r--r--spec/graphql/types/release_links_type_spec.rb7
-rw-r--r--spec/graphql/types/root_storage_statistics_type_spec.rb2
-rw-r--r--spec/graphql/types/security/report_types_enum_spec.rb11
-rw-r--r--spec/graphql/types/terraform/state_type_spec.rb5
-rw-r--r--spec/graphql/types/terraform/state_version_type_spec.rb20
-rw-r--r--spec/graphql/types/user_status_type_spec.rb1
-rw-r--r--spec/graphql/types/user_type_spec.rb1
105 files changed, 2392 insertions, 136 deletions
diff --git a/spec/graphql/mutations/alert_management/http_integration/create_spec.rb b/spec/graphql/mutations/alert_management/http_integration/create_spec.rb
new file mode 100644
index 00000000000..9aa89761aaf
--- /dev/null
+++ b/spec/graphql/mutations/alert_management/http_integration/create_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::AlertManagement::HttpIntegration::Create do
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let(:args) { { project_path: project.full_path, active: true, name: 'HTTP Integration' } }
+
+ specify { expect(described_class).to require_graphql_authorizations(:admin_operations) }
+
+ describe '#resolve' do
+ subject(:resolve) { mutation_for(project, current_user).resolve(args) }
+
+ context 'user has access to project' do
+ before do
+ project.add_maintainer(current_user)
+ end
+
+ context 'when HttpIntegrations::CreateService responds with success' do
+ it 'returns the integration with no errors' do
+ expect(resolve).to eq(
+ integration: ::AlertManagement::HttpIntegration.last!,
+ errors: []
+ )
+ end
+ end
+
+ context 'when HttpIntegrations::CreateService responds with an error' do
+ before do
+ allow_any_instance_of(::AlertManagement::HttpIntegrations::CreateService)
+ .to receive(:execute)
+ .and_return(ServiceResponse.error(payload: { integration: nil }, message: 'An integration already exists'))
+ end
+
+ it 'returns errors' do
+ expect(resolve).to eq(
+ integration: nil,
+ errors: ['An integration already exists']
+ )
+ end
+ end
+ end
+
+ context 'when resource is not accessible to the user' do
+ 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
+ end
+
+ private
+
+ 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/http_integration/destroy_spec.rb b/spec/graphql/mutations/alert_management/http_integration/destroy_spec.rb
new file mode 100644
index 00000000000..f74f9186743
--- /dev/null
+++ b/spec/graphql/mutations/alert_management/http_integration/destroy_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::AlertManagement::HttpIntegration::Destroy do
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let(:integration) { create(:alert_management_http_integration, project: project) }
+ let(:args) { { id: GitlabSchema.id_from_object(integration) } }
+
+ specify { expect(described_class).to require_graphql_authorizations(:admin_operations) }
+
+ describe '#resolve' do
+ subject(:resolve) { mutation_for(project, current_user).resolve(args) }
+
+ context 'user has access to project' do
+ before do
+ project.add_maintainer(current_user)
+ end
+
+ context 'when HttpIntegrations::DestroyService responds with success' do
+ it 'returns the integration with no errors' do
+ expect(resolve).to eq(
+ integration: integration,
+ errors: []
+ )
+ end
+ end
+
+ context 'when HttpIntegrations::DestroyService responds with an error' do
+ before do
+ allow_any_instance_of(::AlertManagement::HttpIntegrations::DestroyService)
+ .to receive(:execute)
+ .and_return(ServiceResponse.error(payload: { integration: nil }, message: 'An error has occurred'))
+ end
+
+ it 'returns errors' do
+ expect(resolve).to eq(
+ integration: nil,
+ errors: ['An error has occurred']
+ )
+ end
+ end
+ end
+
+ context 'when resource is not accessible to the user' do
+ 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
+ end
+
+ private
+
+ 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/http_integration/reset_token_spec.rb b/spec/graphql/mutations/alert_management/http_integration/reset_token_spec.rb
new file mode 100644
index 00000000000..d3ffb2abb47
--- /dev/null
+++ b/spec/graphql/mutations/alert_management/http_integration/reset_token_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::AlertManagement::HttpIntegration::ResetToken do
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:integration) { create(:alert_management_http_integration, project: project) }
+ let(:args) { { id: GitlabSchema.id_from_object(integration) } }
+
+ specify { expect(described_class).to require_graphql_authorizations(:admin_operations) }
+
+ describe '#resolve' do
+ subject(:resolve) { mutation_for(project, current_user).resolve(args) }
+
+ context 'user has sufficient access to project' do
+ before do
+ project.add_maintainer(current_user)
+ end
+
+ context 'when HttpIntegrations::UpdateService responds with success' do
+ it 'returns the integration with no errors' do
+ expect(resolve).to eq(
+ integration: integration,
+ errors: []
+ )
+ end
+ end
+
+ context 'when HttpIntegrations::UpdateService responds with an error' do
+ before do
+ allow_any_instance_of(::AlertManagement::HttpIntegrations::UpdateService)
+ .to receive(:execute)
+ .and_return(ServiceResponse.error(payload: { integration: nil }, message: 'Token cannot be reset'))
+ end
+
+ it 'returns errors' do
+ expect(resolve).to eq(
+ integration: nil,
+ errors: ['Token cannot be reset']
+ )
+ end
+ end
+ end
+
+ context 'when resource is not accessible to the user' do
+ 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
+ end
+
+ private
+
+ 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/http_integration/update_spec.rb b/spec/graphql/mutations/alert_management/http_integration/update_spec.rb
new file mode 100644
index 00000000000..d6318e3161d
--- /dev/null
+++ b/spec/graphql/mutations/alert_management/http_integration/update_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::AlertManagement::HttpIntegration::Update do
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:integration) { create(:alert_management_http_integration, project: project) }
+ let(:args) { { id: GitlabSchema.id_from_object(integration), active: false, name: 'New Name' } }
+
+ specify { expect(described_class).to require_graphql_authorizations(:admin_operations) }
+
+ describe '#resolve' do
+ subject(:resolve) { mutation_for(project, current_user).resolve(args) }
+
+ context 'user has sufficient access to project' do
+ before do
+ project.add_maintainer(current_user)
+ end
+
+ context 'when HttpIntegrations::UpdateService responds with success' do
+ it 'returns the integration with no errors' do
+ expect(resolve).to eq(
+ integration: integration,
+ errors: []
+ )
+ end
+ end
+
+ context 'when HttpIntegrations::UpdateService responds with an error' do
+ before do
+ allow_any_instance_of(::AlertManagement::HttpIntegrations::UpdateService)
+ .to receive(:execute)
+ .and_return(ServiceResponse.error(payload: { integration: nil }, message: 'Failed to update'))
+ end
+
+ it 'returns errors' do
+ expect(resolve).to eq(
+ integration: nil,
+ errors: ['Failed to update']
+ )
+ end
+ end
+ end
+
+ context 'when resource is not accessible to the user' do
+ 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
+ end
+
+ private
+
+ 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/prometheus_integration/create_spec.rb b/spec/graphql/mutations/alert_management/prometheus_integration/create_spec.rb
new file mode 100644
index 00000000000..02a5e2e74e2
--- /dev/null
+++ b/spec/graphql/mutations/alert_management/prometheus_integration/create_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::AlertManagement::PrometheusIntegration::Create do
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let(:args) { { project_path: project.full_path, active: true, api_url: 'http://prometheus.com/' } }
+
+ specify { expect(described_class).to require_graphql_authorizations(:admin_project) }
+
+ describe '#resolve' do
+ subject(:resolve) { mutation_for(project, current_user).resolve(args) }
+
+ context 'user has access to project' do
+ before do
+ project.add_maintainer(current_user)
+ end
+
+ context 'when Prometheus Integration already exists' do
+ let_it_be(:existing_integration) { create(:prometheus_service, project: project) }
+
+ it 'returns errors' do
+ expect(resolve).to eq(
+ integration: nil,
+ errors: ['Multiple Prometheus integrations are not supported']
+ )
+ end
+ end
+
+ context 'when UpdateService responds with success' do
+ it 'returns the integration with no errors' do
+ expect(resolve).to eq(
+ integration: ::PrometheusService.last!,
+ errors: []
+ )
+ end
+
+ it 'creates a corresponding token' do
+ expect { resolve }.to change(::Alerting::ProjectAlertingSetting, :count).by(1)
+ end
+ end
+
+ context 'when UpdateService responds with an error' do
+ before do
+ allow_any_instance_of(::Projects::Operations::UpdateService)
+ .to receive(:execute)
+ .and_return({ status: :error, message: 'An error occurred' })
+ end
+
+ it 'returns errors' do
+ expect(resolve).to eq(
+ integration: nil,
+ errors: ['An error occurred']
+ )
+ end
+ end
+ end
+
+ context 'when resource is not accessible to the user' do
+ 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
+ end
+
+ private
+
+ 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/prometheus_integration/reset_token_spec.rb b/spec/graphql/mutations/alert_management/prometheus_integration/reset_token_spec.rb
new file mode 100644
index 00000000000..45d92695e06
--- /dev/null
+++ b/spec/graphql/mutations/alert_management/prometheus_integration/reset_token_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::AlertManagement::PrometheusIntegration::ResetToken do
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:integration) { create(:prometheus_service, project: project) }
+ let(:args) { { id: GitlabSchema.id_from_object(integration) } }
+
+ specify { expect(described_class).to require_graphql_authorizations(:admin_project) }
+
+ describe '#resolve' do
+ subject(:resolve) { mutation_for(project, current_user).resolve(args) }
+
+ context 'user has sufficient access to project' do
+ before do
+ project.add_maintainer(current_user)
+ end
+
+ context 'when ::Projects::Operations::UpdateService responds with success' do
+ it 'returns the integration with no errors' do
+ expect(resolve).to eq(
+ integration: integration,
+ errors: []
+ )
+ end
+ end
+
+ context 'when ::Projects::Operations::UpdateService responds with an error' do
+ before do
+ allow_any_instance_of(::Projects::Operations::UpdateService)
+ .to receive(:execute)
+ .and_return({ status: :error, message: 'An error occurred' })
+ end
+
+ it 'returns errors' do
+ expect(resolve).to eq(
+ integration: integration,
+ errors: ['An error occurred']
+ )
+ end
+ end
+ end
+
+ context 'when resource is not accessible to the user' do
+ 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
+ end
+
+ private
+
+ 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/prometheus_integration/update_spec.rb b/spec/graphql/mutations/alert_management/prometheus_integration/update_spec.rb
new file mode 100644
index 00000000000..eab4474d827
--- /dev/null
+++ b/spec/graphql/mutations/alert_management/prometheus_integration/update_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::AlertManagement::PrometheusIntegration::Update do
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:integration) { create(:prometheus_service, project: project) }
+ let(:args) { { id: GitlabSchema.id_from_object(integration), active: false, api_url: 'http://new-url.com' } }
+
+ specify { expect(described_class).to require_graphql_authorizations(:admin_project) }
+
+ describe '#resolve' do
+ subject(:resolve) { mutation_for(project, current_user).resolve(args) }
+
+ context 'user has sufficient access to project' do
+ before do
+ project.add_maintainer(current_user)
+ end
+
+ context 'when ::Projects::Operations::UpdateService responds with success' do
+ it 'returns the integration with no errors' do
+ expect(resolve).to eq(
+ integration: integration,
+ errors: []
+ )
+ end
+ end
+
+ context 'when ::Projects::Operations::UpdateService responds with an error' do
+ before do
+ allow_any_instance_of(::Projects::Operations::UpdateService)
+ .to receive(:execute)
+ .and_return({ status: :error, message: 'An error occurred' })
+ end
+
+ it 'returns errors' do
+ expect(resolve).to eq(
+ integration: integration,
+ errors: ['An error occurred']
+ )
+ end
+ end
+ end
+
+ context 'when resource is not accessible to the user' do
+ 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
+ end
+
+ private
+
+ 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/update_alert_status_spec.rb b/spec/graphql/mutations/alert_management/update_alert_status_spec.rb
index ab98088ebcd..08761ce64c2 100644
--- a/spec/graphql/mutations/alert_management/update_alert_status_spec.rb
+++ b/spec/graphql/mutations/alert_management/update_alert_status_spec.rb
@@ -37,8 +37,8 @@ RSpec.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::AlertManagement::AlertResolver) do |resolver|
- allow(resolver).to receive(:resolve).and_return(alert)
+ allow_next_instance_of(::AlertManagement::AlertsFinder) do |finder|
+ allow(finder).to receive(:execute).and_return([alert])
end
allow(alert).to receive(:save).and_return(false)
diff --git a/spec/graphql/mutations/commits/create_spec.rb b/spec/graphql/mutations/commits/create_spec.rb
index fb1baafe7bd..82a5e3a62f5 100644
--- a/spec/graphql/mutations/commits/create_spec.rb
+++ b/spec/graphql/mutations/commits/create_spec.rb
@@ -5,8 +5,9 @@ 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_it_be(:project) { create(:project, :public, :repository) }
+
let(:context) do
GraphQL::Query::Context.new(
query: OpenStruct.new(schema: nil),
@@ -18,9 +19,10 @@ RSpec.describe Mutations::Commits::Create do
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) }
+ subject { mutation.resolve(project_path: project.full_path, branch: branch, start_branch: start_branch, message: message, actions: actions) }
let(:branch) { 'master' }
+ let(:start_branch) { nil }
let(:message) { 'Commit message' }
let(:actions) do
[
@@ -142,6 +144,29 @@ RSpec.describe Mutations::Commits::Create do
end
end
+ context 'when branch does not exist and a start branch is provided' do
+ let(:branch) { 'my-branch' }
+ let(:start_branch) { 'master' }
+ let(:actions) do
+ [
+ {
+ action: 'create',
+ file_path: 'ANOTHER_FILE.md',
+ content: 'Bye'
+ }
+ ]
+ 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_file: true, new_path: 'ANOTHER_FILE.md')
+ ])
+ end
+ end
+
context 'when message is not set' do
let(:message) { nil }
diff --git a/spec/graphql/mutations/container_expiration_policies/update_spec.rb b/spec/graphql/mutations/container_expiration_policies/update_spec.rb
index 6aedaab3b53..9c6016e0af4 100644
--- a/spec/graphql/mutations/container_expiration_policies/update_spec.rb
+++ b/spec/graphql/mutations/container_expiration_policies/update_spec.rb
@@ -35,7 +35,7 @@ RSpec.describe Mutations::ContainerExpirationPolicies::Update do
it_behaves_like 'not creating the container expiration policy'
- it "doesn't update the cadence" do
+ it 'doesn\'t update the cadence' do
expect { subject }
.not_to change { container_expiration_policy.reload.cadence }
end
@@ -47,6 +47,24 @@ RSpec.describe Mutations::ContainerExpirationPolicies::Update do
)
end
end
+
+ context 'with blank regex' do
+ let_it_be(:params) { { project_path: project.full_path, name_regex: '', enabled: true } }
+
+ 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: ['Name regex can\'t be blank']
+ )
+ end
+ end
end
RSpec.shared_examples 'denying access to container expiration policy' do
diff --git a/spec/graphql/mutations/container_repositories/destroy_spec.rb b/spec/graphql/mutations/container_repositories/destroy_spec.rb
new file mode 100644
index 00000000000..3903196a511
--- /dev/null
+++ b/spec/graphql/mutations/container_repositories/destroy_spec.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::ContainerRepositories::Destroy do
+ using RSpec::Parameterized::TableSyntax
+
+ let_it_be_with_reload(:container_repository) { create(:container_repository) }
+ let_it_be(:user) { create(:user) }
+
+ let(:project) { container_repository.project }
+ let(:id) { container_repository.to_global_id.to_s }
+
+ specify { expect(described_class).to require_graphql_authorizations(:destroy_container_image) }
+
+ describe '#resolve' do
+ subject do
+ described_class.new(object: nil, context: { current_user: user }, field: nil)
+ .resolve(id: id)
+ end
+
+ shared_examples 'destroying the container repository' do
+ it 'destroys the container repistory' do
+ expect(::Packages::CreateEventService)
+ .to receive(:new).with(nil, user, event_name: :delete_repository, scope: :container).and_call_original
+ expect(DeleteContainerRepositoryWorker)
+ .to receive(:perform_async).with(user.id, container_repository.id)
+
+ expect { subject }.to change { ::Packages::Event.count }.by(1)
+ expect(container_repository.reload.delete_scheduled?).to be true
+ end
+ end
+
+ shared_examples 'denying access to container respository' do
+ it 'raises an error' do
+ expect(DeleteContainerRepositoryWorker)
+ .not_to receive(:perform_async).with(user.id, container_repository.id)
+
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+
+ context 'with valid id' do
+ where(:user_role, :shared_examples_name) do
+ :maintainer | 'destroying the container repository'
+ :developer | 'destroying the container repository'
+ :reporter | 'denying access to container respository'
+ :guest | 'denying access to container respository'
+ :anonymous | 'denying access to container respository'
+ 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 'with invalid id' do
+ let(:id) { 'gid://gitlab/ContainerRepository/5555' }
+
+ it_behaves_like 'denying access to container respository'
+ end
+ end
+end
diff --git a/spec/graphql/mutations/issues/set_assignees_spec.rb b/spec/graphql/mutations/issues/set_assignees_spec.rb
index 77ba511b715..9a27c5acdac 100644
--- a/spec/graphql/mutations/issues/set_assignees_spec.rb
+++ b/spec/graphql/mutations/issues/set_assignees_spec.rb
@@ -3,6 +3,20 @@
require 'spec_helper'
RSpec.describe Mutations::Issues::SetAssignees do
+ context 'when the user does not have permissions' do
+ let_it_be(:issue) { create(:issue) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:assignee) { create(:user) }
+
+ subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
+
+ describe '#resolve' do
+ subject { mutation.resolve(project_path: issue.project.full_path, iid: issue.iid, assignee_usernames: [assignee.username]) }
+
+ it_behaves_like 'permission level for issue mutation is correctly verified'
+ end
+ end
+
it_behaves_like 'an assignable resource' do
let_it_be(:resource, reload: true) { create(:issue) }
end
diff --git a/spec/graphql/mutations/issues/set_confidential_spec.rb b/spec/graphql/mutations/issues/set_confidential_spec.rb
index 0b2fc0ecb93..c3269e5c0c0 100644
--- a/spec/graphql/mutations/issues/set_confidential_spec.rb
+++ b/spec/graphql/mutations/issues/set_confidential_spec.rb
@@ -17,9 +17,7 @@ RSpec.describe Mutations::Issues::SetConfidential do
subject { mutation.resolve(project_path: project.full_path, iid: issue.iid, confidential: confidential) }
- it 'raises an error if the resource is not accessible to the user' do
- expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
- end
+ it_behaves_like 'permission level for issue mutation is correctly verified'
context 'when the user can update the issue' do
before do
diff --git a/spec/graphql/mutations/issues/set_due_date_spec.rb b/spec/graphql/mutations/issues/set_due_date_spec.rb
index a638971d966..9f8d0d6c405 100644
--- a/spec/graphql/mutations/issues/set_due_date_spec.rb
+++ b/spec/graphql/mutations/issues/set_due_date_spec.rb
@@ -16,9 +16,7 @@ RSpec.describe Mutations::Issues::SetDueDate do
subject { mutation.resolve(project_path: issue.project.full_path, iid: issue.iid, due_date: due_date) }
- it 'raises an error if the resource is not accessible to the user' do
- expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
- end
+ it_behaves_like 'permission level for issue mutation is correctly verified'
context 'when the user can update the issue' do
before do
diff --git a/spec/graphql/mutations/issues/set_locked_spec.rb b/spec/graphql/mutations/issues/set_locked_spec.rb
index 10438226c17..1a0af0c6c63 100644
--- a/spec/graphql/mutations/issues/set_locked_spec.rb
+++ b/spec/graphql/mutations/issues/set_locked_spec.rb
@@ -15,9 +15,7 @@ RSpec.describe Mutations::Issues::SetLocked do
subject { mutation.resolve(project_path: issue.project.full_path, iid: issue.iid, locked: locked) }
- it 'raises an error if the resource is not accessible to the user' do
- expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
- end
+ it_behaves_like 'permission level for issue mutation is correctly verified'
context 'when the user can update the issue' do
let(:mutated_issue) { subject[:issue] }
diff --git a/spec/graphql/mutations/issues/set_severity_spec.rb b/spec/graphql/mutations/issues/set_severity_spec.rb
index ed73d3b777e..7698118ae3e 100644
--- a/spec/graphql/mutations/issues/set_severity_spec.rb
+++ b/spec/graphql/mutations/issues/set_severity_spec.rb
@@ -15,11 +15,7 @@ RSpec.describe Mutations::Issues::SetSeverity do
subject(:resolve) { mutation.resolve(project_path: issue.project.full_path, iid: issue.iid, severity: severity) }
- context 'when the user cannot update the issue' do
- it 'raises an error' do
- expect { resolve }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
- end
- end
+ it_behaves_like 'permission level for issue mutation is correctly verified'
context 'when the user can update the issue' do
before do
diff --git a/spec/graphql/mutations/issues/update_spec.rb b/spec/graphql/mutations/issues/update_spec.rb
index f9f4bdeb6fa..ce1eb874bcf 100644
--- a/spec/graphql/mutations/issues/update_spec.rb
+++ b/spec/graphql/mutations/issues/update_spec.rb
@@ -35,11 +35,7 @@ RSpec.describe Mutations::Issues::Update do
subject { mutation.resolve(mutation_params) }
- context 'when the user cannot access the issue' do
- it 'raises an error' do
- expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
- end
- end
+ it_behaves_like 'permission level for issue mutation is correctly verified'
context 'when the user can update the issue' do
before do
diff --git a/spec/graphql/mutations/labels/create_spec.rb b/spec/graphql/mutations/labels/create_spec.rb
new file mode 100644
index 00000000000..8b284816d63
--- /dev/null
+++ b/spec/graphql/mutations/labels/create_spec.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::Labels::Create do
+ let_it_be(:user) { create(:user) }
+
+ let(:attributes) do
+ {
+ title: 'new title',
+ description: 'A new label'
+ }
+ end
+
+ let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
+ let(:mutated_label) { subject[:label] }
+
+ shared_examples 'create labels mutation' do
+ describe '#resolve' do
+ subject { mutation.resolve(attributes.merge(extra_params)) }
+
+ context 'when the user does not have permission to create a label' do
+ before do
+ parent.add_guest(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 label' do
+ before do
+ parent.add_developer(user)
+ end
+
+ it 'creates label with correct values' do
+ expect(mutated_label).to have_attributes(attributes)
+ end
+ end
+ end
+ end
+
+ specify { expect(described_class).to require_graphql_authorizations(:admin_label) }
+
+ context 'when creating a project label' do
+ let_it_be(:parent) { create(:project) }
+ let(:extra_params) { { project_path: parent.full_path } }
+
+ it_behaves_like 'create labels mutation'
+ end
+
+ context 'when creating a group label' do
+ let_it_be(:parent) { create(:group) }
+ let(:extra_params) { { group_path: parent.full_path } }
+
+ it_behaves_like 'create labels mutation'
+ end
+
+ describe '#ready?' do
+ subject { mutation.ready?(attributes.merge(extra_params)) }
+
+ context 'when passing both project_path and group_path' do
+ let(:extra_params) { { project_path: 'foo', group_path: 'bar' } }
+
+ it 'raises an argument error' do
+ expect { subject }
+ .to raise_error(Gitlab::Graphql::Errors::ArgumentError, /Exactly one of/)
+ end
+ end
+
+ context 'when passing only project_path or group_path' do
+ let(:extra_params) { { project_path: 'foo' } }
+
+ it 'does not raise an error' do
+ expect { subject }.not_to raise_error
+ end
+ end
+ end
+end
diff --git a/spec/graphql/mutations/merge_requests/set_assignees_spec.rb b/spec/graphql/mutations/merge_requests/set_assignees_spec.rb
index 4ac40fc09c6..e2eab591341 100644
--- a/spec/graphql/mutations/merge_requests/set_assignees_spec.rb
+++ b/spec/graphql/mutations/merge_requests/set_assignees_spec.rb
@@ -3,6 +3,20 @@
require 'spec_helper'
RSpec.describe Mutations::MergeRequests::SetAssignees do
+ context 'when the user does not have permissions' do
+ let_it_be(:merge_request) { create(:merge_request) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:assignee) { create(:user) }
+
+ subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
+
+ describe '#resolve' do
+ subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, assignee_usernames: [assignee.username]) }
+
+ it_behaves_like 'permission level for merge request mutation is correctly verified'
+ end
+ end
+
it_behaves_like 'an assignable resource' do
let_it_be(:resource, reload: true) { create(:merge_request) }
end
diff --git a/spec/graphql/mutations/merge_requests/set_labels_spec.rb b/spec/graphql/mutations/merge_requests/set_labels_spec.rb
index 62a7f650f84..1bb303cf99b 100644
--- a/spec/graphql/mutations/merge_requests/set_labels_spec.rb
+++ b/spec/graphql/mutations/merge_requests/set_labels_spec.rb
@@ -18,9 +18,7 @@ RSpec.describe Mutations::MergeRequests::SetLabels do
subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, label_ids: label_ids) }
- it 'raises an error if the resource is not accessible to the user' do
- expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
- end
+ it_behaves_like 'permission level for merge request mutation is correctly verified'
context 'when the user can update the merge request' do
before do
diff --git a/spec/graphql/mutations/merge_requests/set_locked_spec.rb b/spec/graphql/mutations/merge_requests/set_locked_spec.rb
index aca7df5445f..03c709e9bb3 100644
--- a/spec/graphql/mutations/merge_requests/set_locked_spec.rb
+++ b/spec/graphql/mutations/merge_requests/set_locked_spec.rb
@@ -16,9 +16,7 @@ RSpec.describe Mutations::MergeRequests::SetLocked do
subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, locked: locked) }
- it 'raises an error if the resource is not accessible to the user' do
- expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
- end
+ it_behaves_like 'permission level for merge request mutation is correctly verified'
context 'when the user can update the merge request' do
before do
diff --git a/spec/graphql/mutations/merge_requests/set_milestone_spec.rb b/spec/graphql/mutations/merge_requests/set_milestone_spec.rb
index ccb2d9bd132..4de857f43e3 100644
--- a/spec/graphql/mutations/merge_requests/set_milestone_spec.rb
+++ b/spec/graphql/mutations/merge_requests/set_milestone_spec.rb
@@ -18,6 +18,8 @@ RSpec.describe Mutations::MergeRequests::SetMilestone do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
+ it_behaves_like 'permission level for merge request mutation is correctly verified'
+
context 'when the user can update the merge request' do
before do
project.add_developer(user)
diff --git a/spec/graphql/mutations/merge_requests/set_wip_spec.rb b/spec/graphql/mutations/merge_requests/set_wip_spec.rb
index b6cb49724fa..69f6a4328b8 100644
--- a/spec/graphql/mutations/merge_requests/set_wip_spec.rb
+++ b/spec/graphql/mutations/merge_requests/set_wip_spec.rb
@@ -16,9 +16,7 @@ RSpec.describe Mutations::MergeRequests::SetWip do
subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, wip: wip) }
- it 'raises an error if the resource is not accessible to the user' do
- expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
- end
+ it_behaves_like 'permission level for merge request mutation is correctly verified'
context 'when the user can update the merge request' do
before do
diff --git a/spec/graphql/mutations/merge_requests/update_spec.rb b/spec/graphql/mutations/merge_requests/update_spec.rb
index 4a1fdf6e74b..8acd2562ea8 100644
--- a/spec/graphql/mutations/merge_requests/update_spec.rb
+++ b/spec/graphql/mutations/merge_requests/update_spec.rb
@@ -18,9 +18,7 @@ RSpec.describe Mutations::MergeRequests::Update do
mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, **attributes)
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
+ it_behaves_like 'permission level for merge request mutation is correctly verified'
context 'when the user can update the merge request' do
before do
diff --git a/spec/graphql/mutations/notes/reposition_image_diff_note_spec.rb b/spec/graphql/mutations/notes/reposition_image_diff_note_spec.rb
new file mode 100644
index 00000000000..8c22e1a1cb6
--- /dev/null
+++ b/spec/graphql/mutations/notes/reposition_image_diff_note_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::Notes::RepositionImageDiffNote do
+ include GraphqlHelpers
+
+ describe '#resolve' do
+ subject do
+ mutation.resolve({ note: note, position: new_position })
+ end
+
+ let_it_be(:noteable) { create(:merge_request) }
+ let_it_be(:project) { noteable.project }
+ let(:note) { create(:image_diff_note_on_merge_request, noteable: noteable, project: project) }
+
+ let(:mutation) do
+ described_class.new(object: nil, context: { current_user: user }, field: nil)
+ end
+
+ let(:new_position) do
+ { x: 10, y: 11, width: 12, height: 13 }
+ end
+
+ context 'when the user does not have permission' do
+ let(:user) { nil }
+
+ 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(:user) { project.creator }
+ let(:mutated_note) { subject[:note] }
+ let(:errors) { subject[:errors] }
+
+ it 'mutates the note', :aggregate_failures do
+ expect { subject }.to change { note.reset.position.to_h }.to(include(new_position))
+
+ expect(mutated_note).to eq(note)
+ expect(errors).to be_empty
+ end
+
+ context 'when the note is a DiffNote, but not on an image' do
+ let(:note) { create(:diff_note_on_merge_request, noteable: noteable, project: project) }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(
+ Gitlab::Graphql::Errors::ResourceNotAvailable,
+ 'Resource is not an ImageDiffNote'
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/spec/graphql/mutations/releases/create_spec.rb b/spec/graphql/mutations/releases/create_spec.rb
new file mode 100644
index 00000000000..d6305691dac
--- /dev/null
+++ b/spec/graphql/mutations/releases/create_spec.rb
@@ -0,0 +1,133 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::Releases::Create do
+ let_it_be(:project) { create(:project, :public, :repository) }
+ let_it_be(:milestone_12_3) { create(:milestone, project: project, title: '12.3') }
+ let_it_be(:milestone_12_4) { create(:milestone, project: project, title: '12.4') }
+ let_it_be(:reporter) { create(:user) }
+ let_it_be(:developer) { create(:user) }
+
+ let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) }
+
+ let(:tag) { 'v1.1.0'}
+ let(:ref) { 'master'}
+ let(:name) { 'Version 1.0'}
+ let(:description) { 'The first release :rocket:' }
+ let(:released_at) { Time.parse('2018-12-10') }
+ let(:milestones) { [milestone_12_3.title, milestone_12_4.title] }
+ let(:assets) do
+ {
+ links: [
+ {
+ name: 'An asset link',
+ url: 'https://gitlab.example.com/link',
+ filepath: '/permanent/link',
+ link_type: 'other'
+ }
+ ]
+ }
+ end
+
+ let(:mutation_arguments) do
+ {
+ project_path: project.full_path,
+ tag: tag,
+ ref: ref,
+ name: name,
+ description: description,
+ released_at: released_at,
+ milestones: milestones,
+ assets: assets
+ }
+ end
+
+ around do |example|
+ freeze_time { example.run }
+ end
+
+ before do
+ project.add_reporter(reporter)
+ project.add_developer(developer)
+ end
+
+ describe '#resolve' do
+ subject(:resolve) do
+ mutation.resolve(**mutation_arguments)
+ end
+
+ let(:new_release) { subject[:release] }
+
+ context 'when the current user has access to create releases' do
+ let(:current_user) { developer }
+
+ it 'returns no errors' do
+ expect(resolve).to include(errors: [])
+ end
+
+ it 'creates the release with the correct tag' do
+ expect(new_release.tag).to eq(tag)
+ end
+
+ it 'creates the release with the correct name' do
+ expect(new_release.name).to eq(name)
+ end
+
+ it 'creates the release with the correct description' do
+ expect(new_release.description).to eq(description)
+ end
+
+ it 'creates the release with the correct released_at' do
+ expect(new_release.released_at).to eq(released_at)
+ end
+
+ it 'creates the release with the correct created_at' do
+ expect(new_release.created_at).to eq(Time.current)
+ end
+
+ it 'creates the release with the correct milestone associations' do
+ expected_milestone_titles = [milestone_12_3.title, milestone_12_4.title]
+ actual_milestone_titles = new_release.milestones.map { |m| m.title }
+
+ # Right now the milestones are returned in a non-deterministic order.
+ # `match_array` should be updated to `eq` once
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/259012 is addressed.
+ expect(actual_milestone_titles).to match_array(expected_milestone_titles)
+ end
+
+ describe 'asset links' do
+ let(:expected_link) { assets[:links].first }
+ let(:new_link) { new_release.links.first }
+
+ it 'creates a single asset link' do
+ expect(new_release.links.size).to eq(1)
+ end
+
+ it 'creates the link with the correct name' do
+ expect(new_link.name).to eq(expected_link[:name])
+ end
+
+ it 'creates the link with the correct url' do
+ expect(new_link.url).to eq(expected_link[:url])
+ end
+
+ it 'creates the link with the correct link type' do
+ expect(new_link.link_type).to eq(expected_link[:link_type])
+ end
+
+ it 'creates the link with the correct direct filepath' do
+ expect(new_link.filepath).to eq(expected_link[:filepath])
+ end
+ end
+ end
+
+ context "when the current user doesn't have access to create releases" do
+ let(:current_user) { reporter }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+ end
+end
diff --git a/spec/graphql/mutations/terraform/state/delete_spec.rb b/spec/graphql/mutations/terraform/state/delete_spec.rb
new file mode 100644
index 00000000000..313a85a4bac
--- /dev/null
+++ b/spec/graphql/mutations/terraform/state/delete_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::Terraform::State::Delete do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:state) { create(:terraform_state) }
+
+ let(:mutation) do
+ described_class.new(
+ object: double,
+ context: { current_user: user },
+ field: double
+ )
+ end
+
+ it { expect(described_class.graphql_name).to eq('TerraformStateDelete') }
+ it { expect(described_class).to require_graphql_authorizations(:admin_terraform_state) }
+
+ describe '#resolve' do
+ let(:global_id) { state.to_global_id }
+
+ subject { mutation.resolve(id: global_id) }
+
+ context 'user does not have permission' do
+ it 'raises an error', :aggregate_failures do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ expect { state.reload }.not_to raise_error
+ end
+ end
+
+ context 'user has permission' do
+ before do
+ state.project.add_maintainer(user)
+ end
+
+ it 'deletes the state', :aggregate_failures do
+ expect do
+ expect(subject).to eq(errors: [])
+ end.to change { ::Terraform::State.count }.by(-1)
+
+ expect { state.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+
+ context 'with invalid params' do
+ let(:global_id) { user.to_global_id }
+
+ it 'raises an error', :aggregate_failures do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ expect { state.reload }.not_to raise_error
+ end
+ end
+ end
+end
diff --git a/spec/graphql/mutations/terraform/state/lock_spec.rb b/spec/graphql/mutations/terraform/state/lock_spec.rb
new file mode 100644
index 00000000000..c83563040fd
--- /dev/null
+++ b/spec/graphql/mutations/terraform/state/lock_spec.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::Terraform::State::Lock do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:state) { create(:terraform_state) }
+
+ let(:mutation) do
+ described_class.new(
+ object: double,
+ context: { current_user: user },
+ field: double
+ )
+ end
+
+ it { expect(described_class.graphql_name).to eq('TerraformStateLock') }
+ it { expect(described_class).to require_graphql_authorizations(:admin_terraform_state) }
+
+ describe '#resolve' do
+ let(:global_id) { state.to_global_id }
+
+ subject { mutation.resolve(id: global_id) }
+
+ context 'user does not have permission' do
+ it 'raises an error', :aggregate_failures do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ expect(state.reload).not_to be_locked
+ end
+ end
+
+ context 'user has permission' do
+ before do
+ state.project.add_maintainer(user)
+ end
+
+ it 'locks the state', :aggregate_failures do
+ expect(subject).to eq(errors: [])
+
+ expect(state.reload).to be_locked
+ expect(state.locked_by_user).to eq(user)
+ expect(state.lock_xid).to be_present
+ expect(state.locked_at).to be_present
+ end
+
+ context 'state is already locked' do
+ let(:locked_by_user) { create(:user) }
+ let(:state) { create(:terraform_state, :locked, locked_by_user: locked_by_user) }
+
+ it 'does not modify the existing lock', :aggregate_failures do
+ expect(subject).to eq(errors: ['state is already locked'])
+
+ expect(state.reload).to be_locked
+ expect(state.locked_by_user).to eq(locked_by_user)
+ end
+ end
+ end
+
+ context 'with invalid params' do
+ let(:global_id) { user.to_global_id }
+
+ it 'raises an error', :aggregate_failures do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ expect(state.reload).not_to be_locked
+ end
+ end
+ end
+end
diff --git a/spec/graphql/mutations/terraform/state/unlock_spec.rb b/spec/graphql/mutations/terraform/state/unlock_spec.rb
new file mode 100644
index 00000000000..4918a1c4abf
--- /dev/null
+++ b/spec/graphql/mutations/terraform/state/unlock_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::Terraform::State::Unlock do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:state) { create(:terraform_state, :locked) }
+
+ let(:mutation) do
+ described_class.new(
+ object: double,
+ context: { current_user: user },
+ field: double
+ )
+ end
+
+ it { expect(described_class.graphql_name).to eq('TerraformStateUnlock') }
+ it { expect(described_class).to require_graphql_authorizations(:admin_terraform_state) }
+
+ describe '#resolve' do
+ let(:global_id) { state.to_global_id }
+
+ subject { mutation.resolve(id: global_id) }
+
+ context 'user does not have permission' do
+ it 'raises an error', :aggregate_failures do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ expect(state.reload).to be_locked
+ end
+ end
+
+ context 'user has permission' do
+ before do
+ state.project.add_maintainer(user)
+ end
+
+ it 'unlocks the state', :aggregate_failures do
+ expect(subject).to eq(errors: [])
+ expect(state.reload).not_to be_locked
+ end
+
+ context 'state is already unlocked' do
+ let(:state) { create(:terraform_state) }
+
+ it 'does not modify the state' do
+ expect(subject).to eq(errors: [])
+ expect(state.reload).not_to be_locked
+ end
+ end
+ end
+
+ context 'with invalid params' do
+ let(:global_id) { user.to_global_id }
+
+ it 'raises an error', :aggregate_failures do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ expect(state.reload).to be_locked
+ end
+ end
+ end
+end
diff --git a/spec/graphql/mutations/todos/create_spec.rb b/spec/graphql/mutations/todos/create_spec.rb
new file mode 100644
index 00000000000..bbb033e2f33
--- /dev/null
+++ b/spec/graphql/mutations/todos/create_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::Todos::Create do
+ include GraphqlHelpers
+ include DesignManagementTestHelpers
+
+ describe '#resolve' do
+ context 'when target does not support todos' do
+ it 'raises error' do
+ current_user = create(:user)
+ mutation = described_class.new(object: nil, context: { current_user: current_user }, field: nil)
+
+ target = create(:milestone)
+
+ expect { mutation.resolve(target_id: global_id_of(target)) }
+ .to raise_error(GraphQL::CoercionError)
+ end
+ end
+
+ context 'with issue as target' do
+ it_behaves_like 'create todo mutation' do
+ let_it_be(:target) { create(:issue) }
+ end
+ end
+
+ context 'with merge request as target' do
+ it_behaves_like 'create todo mutation' do
+ let_it_be(:target) { create(:merge_request) }
+ end
+ end
+
+ context 'with design as target' do
+ before do
+ enable_design_management
+ end
+
+ it_behaves_like 'create todo mutation' do
+ let_it_be(:target) { create(:design) }
+ end
+ end
+ end
+end
diff --git a/spec/graphql/mutations/todos/mark_all_done_spec.rb b/spec/graphql/mutations/todos/mark_all_done_spec.rb
index 2f167164050..f3b6bf52ef7 100644
--- a/spec/graphql/mutations/todos/mark_all_done_spec.rb
+++ b/spec/graphql/mutations/todos/mark_all_done_spec.rb
@@ -28,7 +28,7 @@ RSpec.describe Mutations::Todos::MarkAllDone do
expect(todo3.reload.state).to eq('done')
expect(other_user_todo.reload.state).to eq('pending')
- expect(updated_todo_ids).to contain_exactly(global_id_of(todo1), global_id_of(todo3))
+ expect(updated_todo_ids).to contain_exactly(todo1.id, todo3.id)
expect(todos).to contain_exactly(todo1, todo3)
end
diff --git a/spec/graphql/mutations/todos/restore_many_spec.rb b/spec/graphql/mutations/todos/restore_many_spec.rb
index 59995e33f2d..dc10355ef22 100644
--- a/spec/graphql/mutations/todos/restore_many_spec.rb
+++ b/spec/graphql/mutations/todos/restore_many_spec.rb
@@ -24,11 +24,11 @@ RSpec.describe Mutations::Todos::RestoreMany do
expect(todo2.reload.state).to eq('pending')
expect(other_user_todo.reload.state).to eq('done')
- todo_ids = result[:updated_ids]
- expect(todo_ids.size).to eq(1)
- expect(todo_ids.first).to eq(todo1.to_global_id.to_s)
-
- expect(result[:todos]).to contain_exactly(todo1)
+ expect(result).to match(
+ errors: be_empty,
+ updated_ids: contain_exactly(todo1.id),
+ todos: contain_exactly(todo1)
+ )
end
it 'handles a todo which is already pending as expected' do
@@ -36,8 +36,11 @@ RSpec.describe Mutations::Todos::RestoreMany do
expect_states_were_not_changed
- expect(result[:updated_ids]).to eq([])
- expect(result[:todos]).to be_empty
+ expect(result).to match(
+ errors: be_empty,
+ updated_ids: be_empty,
+ todos: be_empty
+ )
end
it 'ignores requests for todos which do not belong to the current user' do
@@ -61,7 +64,7 @@ RSpec.describe Mutations::Todos::RestoreMany do
expect(result[:updated_ids].size).to eq(2)
returned_todo_ids = result[:updated_ids]
- expect(returned_todo_ids).to contain_exactly(todo1.to_global_id.to_s, todo4.to_global_id.to_s)
+ expect(returned_todo_ids).to contain_exactly(todo1.id, todo4.id)
expect(result[:todos]).to contain_exactly(todo1, todo4)
expect(todo1.reload.state).to eq('pending')
diff --git a/spec/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver_spec.rb b/spec/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver_spec.rb
index c5637d43382..578d679ade4 100644
--- a/spec/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver_spec.rb
+++ b/spec/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver_spec.rb
@@ -14,7 +14,9 @@ RSpec.describe Resolvers::Admin::Analytics::InstanceStatistics::MeasurementsReso
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) }
- subject { resolve_measurements({ identifier: 'projects' }, { current_user: current_user }) }
+ 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
@@ -40,6 +42,24 @@ RSpec.describe Resolvers::Admin::Analytics::InstanceStatistics::MeasurementsReso
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
diff --git a/spec/graphql/resolvers/alert_management/integrations_resolver_spec.rb b/spec/graphql/resolvers/alert_management/integrations_resolver_spec.rb
new file mode 100644
index 00000000000..36e409e0677
--- /dev/null
+++ b/spec/graphql/resolvers/alert_management/integrations_resolver_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Resolvers::AlertManagement::IntegrationsResolver do
+ include GraphqlHelpers
+
+ let_it_be(:current_user) { 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) }
+
+ specify do
+ expect(described_class).to have_nullable_graphql_type(Types::AlertManagement::IntegrationType.connection_type)
+ end
+
+ context 'user does not have permission' do
+ it { is_expected.to be_empty }
+ end
+
+ context 'user has permission' do
+ before do
+ project.add_maintainer(current_user)
+ end
+
+ it { is_expected.to contain_exactly(active_http_integration, prometheus_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/base_resolver_spec.rb b/spec/graphql/resolvers/base_resolver_spec.rb
index 40dc2370052..e5b9fb57e42 100644
--- a/spec/graphql/resolvers/base_resolver_spec.rb
+++ b/spec/graphql/resolvers/base_resolver_spec.rb
@@ -7,10 +7,13 @@ RSpec.describe Resolvers::BaseResolver do
let(:resolver) do
Class.new(described_class) do
- def resolve(**args)
+ argument :test, ::GraphQL::INT_TYPE, required: false
+ type [::GraphQL::INT_TYPE], null: true
+
+ def resolve(test: 100)
process(object)
- [args, args]
+ [test, test]
end
def process(obj); end
@@ -19,17 +22,75 @@ RSpec.describe Resolvers::BaseResolver do
let(:last_resolver) do
Class.new(described_class) do
+ type [::GraphQL::INT_TYPE], null: true
+
def resolve(**args)
[1, 2]
end
end
end
+ describe '.singular_type' do
+ subject { resolver.singular_type }
+
+ context 'for a connection of scalars' do
+ let(:resolver) do
+ Class.new(described_class) do
+ type ::GraphQL::INT_TYPE.connection_type, null: true
+ end
+ end
+
+ it { is_expected.to eq(::GraphQL::INT_TYPE) }
+ end
+
+ context 'for a connection of objects' do
+ let(:object) do
+ Class.new(::Types::BaseObject) do
+ graphql_name 'Foo'
+ end
+ end
+
+ let(:resolver) do
+ conn = object.connection_type
+
+ Class.new(described_class) do
+ type conn, null: true
+ end
+ end
+
+ it { is_expected.to eq(object) }
+ end
+
+ context 'for a list type' do
+ let(:resolver) do
+ Class.new(described_class) do
+ type [::GraphQL::STRING_TYPE], null: true
+ end
+ end
+
+ it { is_expected.to eq(::GraphQL::STRING_TYPE) }
+ end
+
+ context 'for a scalar type' do
+ let(:resolver) do
+ Class.new(described_class) do
+ type ::GraphQL::BOOLEAN_TYPE, null: true
+ end
+ end
+
+ it { is_expected.to eq(::GraphQL::BOOLEAN_TYPE) }
+ end
+ end
+
describe '.single' do
it 'returns a subclass from the resolver' do
expect(resolver.single.superclass).to eq(resolver)
end
+ it 'has the correct (singular) type' do
+ expect(resolver.single.type).to eq(::GraphQL::INT_TYPE)
+ end
+
it 'returns the same subclass every time' do
expect(resolver.single.object_id).to eq(resolver.single.object_id)
end
@@ -37,15 +98,106 @@ RSpec.describe Resolvers::BaseResolver do
it 'returns a resolver that gives the first result from the original resolver' do
result = resolve(resolver.single, args: { test: 1 })
- expect(result).to eq(test: 1)
+ expect(result).to eq(1)
+ end
+ end
+
+ describe '.when_single' do
+ let(:resolver) do
+ Class.new(described_class) do
+ type [::GraphQL::INT_TYPE], null: true
+
+ when_single do
+ argument :foo, ::GraphQL::INT_TYPE, required: true
+ end
+
+ def resolve(foo: 1)
+ [foo * foo] # rubocop: disable Lint/BinaryOperatorWithIdenticalOperands
+ end
+ end
+ end
+
+ it 'does not apply the block to the resolver' do
+ expect(resolver.field_options).to include(
+ arguments: be_empty
+ )
+ result = resolve(resolver)
+
+ expect(result).to eq([1])
+ end
+
+ it 'applies the block to the single version of the resolver' do
+ expect(resolver.single.field_options).to include(
+ arguments: match('foo' => an_instance_of(::Types::BaseArgument))
+ )
+ result = resolve(resolver.single, args: { foo: 7 })
+
+ expect(result).to eq(49)
+ end
+
+ context 'multiple when_single blocks' do
+ let(:resolver) do
+ Class.new(described_class) do
+ type [::GraphQL::INT_TYPE], null: true
+
+ when_single do
+ argument :foo, ::GraphQL::INT_TYPE, required: true
+ end
+
+ when_single do
+ argument :bar, ::GraphQL::INT_TYPE, required: true
+ end
+
+ def resolve(foo: 1, bar: 2)
+ [foo * bar]
+ end
+ end
+ end
+
+ it 'applies both blocks to the single version of the resolver' do
+ expect(resolver.single.field_options).to include(
+ arguments: match('foo' => ::Types::BaseArgument, 'bar' => ::Types::BaseArgument)
+ )
+ result = resolve(resolver.single, args: { foo: 7, bar: 5 })
+
+ expect(result).to eq(35)
+ end
+ end
+
+ context 'inheritance' do
+ let(:subclass) do
+ Class.new(resolver) do
+ when_single do
+ argument :inc, ::GraphQL::INT_TYPE, required: true
+ end
+
+ def resolve(foo:, inc:)
+ super(foo: foo + inc)
+ end
+ end
+ end
+
+ it 'applies both blocks to the single version of the resolver' do
+ expect(resolver.single.field_options).to include(
+ arguments: match('foo' => ::Types::BaseArgument)
+ )
+ expect(subclass.single.field_options).to include(
+ arguments: match('foo' => ::Types::BaseArgument, 'inc' => ::Types::BaseArgument)
+ )
+ result = resolve(subclass.single, args: { foo: 7, inc: 1 })
+
+ expect(result).to eq(64)
+ end
end
end
context 'when the resolver returns early' do
let(:resolver) do
Class.new(described_class) do
+ type [::GraphQL::STRING_TYPE], null: true
+
def ready?(**args)
- [false, %w(early return)]
+ [false, %w[early return]]
end
def resolve(**args)
@@ -121,28 +273,4 @@ RSpec.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/ci/jobs_resolver_spec.rb b/spec/graphql/resolvers/ci/jobs_resolver_spec.rb
new file mode 100644
index 00000000000..a836c89bd61
--- /dev/null
+++ b/spec/graphql/resolvers/ci/jobs_resolver_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Resolvers::Ci::JobsResolver do
+ include GraphqlHelpers
+
+ let_it_be(:pipeline) { create(:ci_pipeline) }
+
+ before_all do
+ create(:ci_build, name: 'Normal job', pipeline: pipeline)
+ create(:ci_build, :sast, name: 'DAST job', pipeline: pipeline)
+ create(:ci_build, :dast, name: 'SAST job', pipeline: pipeline)
+ create(:ci_build, :container_scanning, name: 'Container scanning job', pipeline: pipeline)
+ end
+
+ describe '#resolve' do
+ context 'when security_report_types is empty' do
+ it "returns all of the pipeline's jobs" do
+ jobs = resolve(described_class, obj: pipeline, args: {}, ctx: {})
+
+ job_names = jobs.map(&:name)
+ expect(job_names).to contain_exactly('Normal job', 'DAST job', 'SAST job', 'Container scanning job')
+ end
+ end
+
+ context 'when security_report_types is present' do
+ it "returns the pipeline's jobs with the given security report types" do
+ report_types = [
+ ::Types::Security::ReportTypeEnum.values['SAST'].value,
+ ::Types::Security::ReportTypeEnum.values['DAST'].value
+ ]
+ jobs = resolve(described_class, obj: pipeline, args: { security_report_types: report_types }, ctx: {})
+
+ job_names = jobs.map(&:name)
+ expect(job_names).to contain_exactly('DAST job', 'SAST job')
+ end
+ end
+ end
+end
diff --git a/spec/graphql/resolvers/ci/runner_setup_resolver_spec.rb b/spec/graphql/resolvers/ci/runner_setup_resolver_spec.rb
new file mode 100644
index 00000000000..3d004290d9b
--- /dev/null
+++ b/spec/graphql/resolvers/ci/runner_setup_resolver_spec.rb
@@ -0,0 +1,104 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Resolvers::Ci::RunnerSetupResolver do
+ include GraphqlHelpers
+
+ describe '#resolve' do
+ let(:user) { create(:user) }
+
+ subject(:resolve_subject) { resolve(described_class, ctx: { current_user: user }, args: { platform: platform, architecture: 'amd64' }.merge(target_param)) }
+
+ context 'with container platforms' do
+ let(:platform) { 'docker' }
+ let(:project) { create(:project) }
+ let(:target_param) { { project_id: project.to_global_id } }
+
+ it 'returns install instructions' do
+ expect(resolve_subject[:install_instructions]).not_to eq(nil)
+ end
+
+ it 'does not return register instructions' do
+ expect(resolve_subject[:register_instructions]).to eq(nil)
+ end
+ end
+
+ context 'with regular platforms' do
+ let(:platform) { 'linux' }
+
+ context 'without target parameter' do
+ let(:target_param) { {} }
+
+ context 'when user is not admin' do
+ it 'returns access error' do
+ expect { resolve_subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+
+ context 'when user is admin' do
+ before do
+ user.update!(admin: true)
+ end
+
+ it 'returns install and register instructions' do
+ expect(resolve_subject.keys).to contain_exactly(:install_instructions, :register_instructions)
+ expect(resolve_subject.values).not_to include(nil)
+ end
+ end
+ end
+
+ context 'with project target parameter' do
+ let(:project) { create(:project) }
+ let(:target_param) { { project_id: project.to_global_id } }
+
+ context 'when user has access to admin builds on project' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ it 'returns install and register instructions' do
+ expect(resolve_subject.keys).to contain_exactly(:install_instructions, :register_instructions)
+ expect(resolve_subject.values).not_to include(nil)
+ end
+ end
+
+ context 'when user does not have access to admin builds on project' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'returns access error' do
+ expect { resolve_subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+ end
+
+ context 'with group target parameter' do
+ let(:group) { create(:group) }
+ let(:target_param) { { group_id: group.to_global_id } }
+
+ context 'when user has access to admin builds on group' do
+ before do
+ group.add_owner(user)
+ end
+
+ it 'returns install and register instructions' do
+ expect(resolve_subject.keys).to contain_exactly(:install_instructions, :register_instructions)
+ expect(resolve_subject.values).not_to include(nil)
+ end
+ end
+
+ context 'when user does not have access to admin builds on group' do
+ before do
+ group.add_developer(user)
+ end
+
+ it 'returns access error' do
+ expect { resolve_subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb b/spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb
new file mode 100644
index 00000000000..b6fe94a2312
--- /dev/null
+++ b/spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb
@@ -0,0 +1,208 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::CachingArrayResolver do
+ include GraphqlHelpers
+
+ let_it_be(:non_admins) { create_list(:user, 4, admin: false) }
+ let(:query_context) { {} }
+ let(:max_page_size) { 10 }
+ let(:field) { double('Field', max_page_size: max_page_size) }
+ let(:schema) { double('Schema', default_max_page_size: 3) }
+
+ let_it_be(:caching_resolver) do
+ mod = described_class
+
+ Class.new(::Resolvers::BaseResolver) do
+ include mod
+
+ def query_input(is_admin:)
+ is_admin
+ end
+
+ def query_for(is_admin)
+ if is_admin.nil?
+ model_class.all
+ else
+ model_class.where(admin: is_admin)
+ end
+ end
+
+ def model_class
+ User # Happens to include FromUnion, and is cheap-ish to create
+ end
+ end
+ end
+
+ describe '#resolve' do
+ context 'there are more than MAX_UNION_SIZE queries' do
+ let_it_be(:max_union) { 3 }
+ let_it_be(:resolver) do
+ mod = described_class
+ max = max_union
+
+ Class.new(::Resolvers::BaseResolver) do
+ include mod
+
+ def query_input(username:)
+ username
+ end
+
+ def query_for(username)
+ if username.nil?
+ model_class.all
+ else
+ model_class.where(username: username)
+ end
+ end
+
+ def model_class
+ User # Happens to include FromUnion, and is cheap-ish to create
+ end
+
+ define_method :max_union_size do
+ max
+ end
+ end
+ end
+
+ it 'executes the queries in multiple batches' do
+ users = create_list(:user, (max_union * 2) + 1)
+ expect(User).to receive(:from_union).twice.and_call_original
+
+ results = users.in_groups_of(2, false).map do |users|
+ resolve(resolver, args: { username: users.map(&:username) }, field: field, schema: schema)
+ end
+
+ expect(results.flat_map(&method(:force))).to match_array(users)
+ end
+ end
+
+ context 'all queries return results' do
+ let_it_be(:admins) { create_list(:admin, 3) }
+
+ it 'batches the queries' do
+ expect do
+ [resolve_users(true), resolve_users(false)].each(&method(:force))
+ end.to issue_same_number_of_queries_as { force(resolve_users(nil)) }
+ end
+
+ it 'finds the correct values' do
+ found_admins = resolve_users(true)
+ found_others = resolve_users(false)
+ admins_again = resolve_users(true)
+ found_all = resolve_users(nil)
+
+ expect(force(found_admins)).to match_array(admins)
+ expect(force(found_others)).to match_array(non_admins)
+ expect(force(admins_again)).to match_array(admins)
+ expect(force(found_all)).to match_array(admins + non_admins)
+ end
+ end
+
+ it 'does not perform a union of a query with itself' do
+ expect(User).to receive(:where).once.and_call_original
+
+ [resolve_users(false), resolve_users(false)].each(&method(:force))
+ end
+
+ context 'one of the queries returns no results' do
+ it 'finds the correct values' do
+ found_admins = resolve_users(true)
+ found_others = resolve_users(false)
+ found_all = resolve_users(nil)
+
+ expect(force(found_admins)).to be_empty
+ expect(force(found_others)).to match_array(non_admins)
+ expect(force(found_all)).to match_array(non_admins)
+ end
+ end
+
+ context 'one of the queries has already been cached' do
+ before do
+ force(resolve_users(nil))
+ end
+
+ it 'avoids further queries' do
+ expect do
+ repeated_find = resolve_users(nil)
+
+ expect(force(repeated_find)).to match_array(non_admins)
+ end.not_to exceed_query_limit(0)
+ end
+ end
+
+ context 'the resolver overrides item_found' do
+ let_it_be(:admins) { create_list(:admin, 2) }
+ let(:query_context) do
+ {
+ found: { true => [], false => [], nil => [] }
+ }
+ end
+
+ let_it_be(:with_item_found) do
+ Class.new(caching_resolver) do
+ def item_found(key, item)
+ context[:found][key] << item
+ end
+ end
+ end
+
+ it 'receives item_found for each key the item mapped to' do
+ found_admins = resolve_users(true, with_item_found)
+ found_all = resolve_users(nil, with_item_found)
+
+ [found_admins, found_all].each(&method(:force))
+
+ expect(query_context[:found]).to match({
+ false => be_empty,
+ true => match_array(admins),
+ nil => match_array(admins + non_admins)
+ })
+ end
+ end
+
+ context 'the max_page_size is lower than the total result size' do
+ let(:max_page_size) { 2 }
+
+ it 'respects the max_page_size, on a per subset basis' do
+ found_all = resolve_users(nil)
+ found_others = resolve_users(false)
+
+ expect(force(found_all).size).to eq(2)
+ expect(force(found_others).size).to eq(2)
+ end
+ end
+
+ context 'the field does not declare max_page_size' do
+ let(:max_page_size) { nil }
+
+ it 'takes the page size from schema.default_max_page_size' do
+ found_all = resolve_users(nil)
+ found_others = resolve_users(false)
+
+ expect(force(found_all).size).to eq(schema.default_max_page_size)
+ expect(force(found_others).size).to eq(schema.default_max_page_size)
+ end
+ end
+
+ specify 'force . resolve === to_a . query_for . query_input' do
+ r = resolver_instance(caching_resolver)
+ args = { is_admin: false }
+
+ naive = r.query_for(r.query_input(**args)).to_a
+
+ expect(force(r.resolve(**args))).to eq(naive)
+ end
+ end
+
+ def resolve_users(is_admin, resolver = caching_resolver)
+ args = { is_admin: is_admin }
+ resolve(resolver, args: args, field: field, ctx: query_context, schema: schema)
+ end
+
+ def force(lazy)
+ ::Gitlab::Graphql::Lazy.force(lazy)
+ end
+end
diff --git a/spec/graphql/resolvers/container_repositories_resolver_spec.rb b/spec/graphql/resolvers/container_repositories_resolver_spec.rb
new file mode 100644
index 00000000000..b888d79626e
--- /dev/null
+++ b/spec/graphql/resolvers/container_repositories_resolver_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Resolvers::ContainerRepositoriesResolver do
+ include GraphqlHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be_with_reload(:project) { create(:project, group: group) }
+ let_it_be(:container_repositories) { create(:container_repository, project: project) }
+
+ let(:args) { {} }
+
+ describe '#resolve' do
+ let(:object) { project }
+
+ subject { resolve(described_class, ctx: { current_user: user }, args: args, obj: object) }
+
+ shared_examples 'returning container repositories' do
+ it { is_expected.to contain_exactly(container_repositories) }
+
+ context 'with a named search' do
+ let_it_be(:named_container_repository) { create(:container_repository, project: project, name: 'Foobar') }
+
+ let(:args) { { name: 'ooba' } }
+
+ it { is_expected.to contain_exactly(named_container_repository) }
+ end
+ end
+
+ context 'with authorized user' do
+ before do
+ group.add_user(user, :maintainer)
+ end
+
+ context 'when the object is a project' do
+ it_behaves_like 'returning container repositories'
+ end
+
+ context 'when the object is a group' do
+ let(:object) { group }
+
+ it_behaves_like 'returning container repositories'
+ end
+
+ context 'when the object is an invalid type' do
+ let(:object) { Object.new }
+
+ it { expect { subject }.to raise_exception('invalid subject_type') }
+ end
+ end
+
+ context 'with unauthorized user' do
+ it { is_expected.to be nil }
+ end
+ end
+end
diff --git a/spec/graphql/resolvers/design_management/design_resolver_spec.rb b/spec/graphql/resolvers/design_management/design_resolver_spec.rb
index 02d7f94612c..e33eaedf167 100644
--- a/spec/graphql/resolvers/design_management/design_resolver_spec.rb
+++ b/spec/graphql/resolvers/design_management/design_resolver_spec.rb
@@ -6,6 +6,10 @@ RSpec.describe Resolvers::DesignManagement::DesignResolver do
include GraphqlHelpers
include DesignManagementTestHelpers
+ specify do
+ expect(described_class).to have_nullable_graphql_type(::Types::DesignManagement::DesignType)
+ end
+
before do
enable_design_management
end
@@ -57,12 +61,21 @@ RSpec.describe Resolvers::DesignManagement::DesignResolver do
end
context 'the ID belongs to a design on another issue' do
- let(:args) { { id: GitlabSchema.id_from_object(design_on_other_issue).to_s } }
+ let(:args) { { id: global_id_of(design_on_other_issue) } }
it 'returns nothing' do
expect(resolve_design).to be_nil
end
end
+
+ context 'the ID does not belong to a design at all' do
+ let(:args) { { id: global_id_of(issue) } }
+ let(:msg) { /does not represent an instance of DesignManagement::Design/ }
+
+ it 'complains meaningfully' do
+ expect { resolve_design }.to raise_error(msg)
+ end
+ end
end
context 'by filename' do
diff --git a/spec/graphql/resolvers/design_management/designs_resolver_spec.rb b/spec/graphql/resolvers/design_management/designs_resolver_spec.rb
index cfa37d34fd9..28e963c88a9 100644
--- a/spec/graphql/resolvers/design_management/designs_resolver_spec.rb
+++ b/spec/graphql/resolvers/design_management/designs_resolver_spec.rb
@@ -6,6 +6,10 @@ RSpec.describe Resolvers::DesignManagement::DesignsResolver do
include GraphqlHelpers
include DesignManagementTestHelpers
+ specify do
+ expect(described_class).to have_nullable_graphql_type(::Types::DesignManagement::DesignType.connection_type)
+ end
+
before do
enable_design_management
end
@@ -65,8 +69,24 @@ RSpec.describe Resolvers::DesignManagement::DesignsResolver do
let(:second_version) { create(:design_version) }
let(:second_design) { create(:design, issue: issue, versions: [second_version]) }
+ context 'ids is provided but null' do
+ let(:args) { { ids: nil } }
+
+ it 'behaves as if unfiltered' do
+ expect(resolve_designs).to contain_exactly(first_design, second_design)
+ end
+ end
+
+ context 'ids is provided but empty' do
+ let(:args) { { ids: [] } }
+
+ it 'eliminates all values' do
+ expect(resolve_designs).to be_empty
+ end
+ end
+
context 'the ID is on the current issue' do
- let(:args) { { ids: [GitlabSchema.id_from_object(second_design).to_s] } }
+ let(:args) { { ids: [GitlabSchema.id_from_object(second_design)] } }
it 'resolves to just the relevant design' do
expect(resolve_designs).to contain_exactly(second_design)
@@ -77,7 +97,7 @@ RSpec.describe Resolvers::DesignManagement::DesignsResolver do
let(:third_version) { create(:design_version) }
let(:third_design) { create(:design, issue: create(:issue, project: project), versions: [third_version]) }
- let(:args) { { ids: [GitlabSchema.id_from_object(third_design).to_s] } }
+ let(:args) { { ids: [GitlabSchema.id_from_object(third_design)] } }
it 'ignores it' do
expect(resolve_designs).to be_empty
diff --git a/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb b/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb
index 8ad928e9854..403261fc22a 100644
--- a/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb
+++ b/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb
@@ -32,7 +32,7 @@ RSpec.describe Resolvers::DesignManagement::VersionInCollectionResolver do
end
context 'we pass an id' do
- let(:params) { { id: global_id_of(first_version) } }
+ let(:params) { { version_id: global_id_of(first_version) } }
it { is_expected.to eq(first_version) }
end
@@ -44,13 +44,14 @@ RSpec.describe Resolvers::DesignManagement::VersionInCollectionResolver do
end
context 'we pass an inconsistent mixture of sha and version id' do
- let(:params) { { sha: first_version.sha, id: global_id_of(create(:design_version)) } }
+ let(:params) { { sha: first_version.sha, version_id: global_id_of(create(:design_version)) } }
it { is_expected.to be_nil }
end
context 'we pass the id of something that is not a design_version' do
- let(:params) { { id: global_id_of(project) } }
+ let(:params) { { version_id: global_id_of(project) } }
+ let(:appropriate_error) { ::GraphQL::CoercionError }
it 'raises an appropriate error' do
expect { result }.to raise_error(appropriate_error)
diff --git a/spec/graphql/resolvers/echo_resolver_spec.rb b/spec/graphql/resolvers/echo_resolver_spec.rb
index 2182ac221f6..4f48e5e0d7a 100644
--- a/spec/graphql/resolvers/echo_resolver_spec.rb
+++ b/spec/graphql/resolvers/echo_resolver_spec.rb
@@ -8,6 +8,10 @@ RSpec.describe Resolvers::EchoResolver do
let(:current_user) { create(:user) }
let(:text) { 'Message test' }
+ specify do
+ expect(described_class).to have_non_null_graphql_type(::GraphQL::STRING_TYPE)
+ end
+
describe '#resolve' do
it 'echoes text and username' do
expect(resolve_echo(text)).to eq %Q("#{current_user.username}" says: #{text})
diff --git a/spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb b/spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb
index 7e531910184..bf8d2139c82 100644
--- a/spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb
+++ b/spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb
@@ -10,6 +10,10 @@ RSpec.describe Resolvers::ErrorTracking::SentryDetailedErrorResolver do
let(:issue_details_service) { spy('ErrorTracking::IssueDetailsService') }
+ specify do
+ expect(described_class).to have_nullable_graphql_type(Types::ErrorTracking::SentryDetailedErrorType)
+ end
+
before do
project.add_developer(current_user)
@@ -61,7 +65,9 @@ RSpec.describe Resolvers::ErrorTracking::SentryDetailedErrorResolver do
context 'blank id' do
let(:args) { { id: '' } }
- it_behaves_like 'it resolves to nil'
+ it 'responds with an error' do
+ expect { resolve_error(args) }.to raise_error(::GraphQL::CoercionError)
+ end
end
end
diff --git a/spec/graphql/resolvers/error_tracking/sentry_error_collection_resolver_spec.rb b/spec/graphql/resolvers/error_tracking/sentry_error_collection_resolver_spec.rb
index 02e0420be2a..20c2bdcd4e1 100644
--- a/spec/graphql/resolvers/error_tracking/sentry_error_collection_resolver_spec.rb
+++ b/spec/graphql/resolvers/error_tracking/sentry_error_collection_resolver_spec.rb
@@ -10,6 +10,10 @@ RSpec.describe Resolvers::ErrorTracking::SentryErrorCollectionResolver do
let(:list_issues_service) { spy('ErrorTracking::ListIssuesService') }
+ specify do
+ expect(described_class).to have_nullable_graphql_type(Types::ErrorTracking::SentryErrorCollectionType)
+ end
+
before do
project.add_developer(current_user)
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 554873a6e21..edca11f40d7 100644
--- a/spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb
+++ b/spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb
@@ -14,6 +14,10 @@ RSpec.describe Resolvers::ErrorTracking::SentryErrorsResolver do
let(:issues) { nil }
let(:pagination) { nil }
+ specify do
+ expect(described_class).to have_nullable_graphql_type(Types::ErrorTracking::SentryErrorType.connection_type)
+ end
+
describe '#resolve' do
context 'insufficient user permission' do
let(:user) { create(:user) }
diff --git a/spec/graphql/resolvers/group_members_resolver_spec.rb b/spec/graphql/resolvers/group_members_resolver_spec.rb
index bbfea575492..bd0b4870062 100644
--- a/spec/graphql/resolvers/group_members_resolver_spec.rb
+++ b/spec/graphql/resolvers/group_members_resolver_spec.rb
@@ -5,6 +5,10 @@ require 'spec_helper'
RSpec.describe Resolvers::GroupMembersResolver do
include GraphqlHelpers
+ specify do
+ expect(described_class).to have_nullable_graphql_type(Types::GroupMemberType.connection_type)
+ end
+
it_behaves_like 'querying members with a group' do
let_it_be(:resource_member) { create(:group_member, user: user_1, group: group_1) }
let_it_be(:resource) { group_1 }
diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb
index 3a6507f906c..43cbd4d2bdd 100644
--- a/spec/graphql/resolvers/issues_resolver_spec.rb
+++ b/spec/graphql/resolvers/issues_resolver_spec.rb
@@ -20,6 +20,10 @@ RSpec.describe Resolvers::IssuesResolver do
let_it_be(:label1) { create(:label, project: project) }
let_it_be(:label2) { create(:label, project: project) }
+ specify do
+ expect(described_class).to have_nullable_graphql_type(Types::IssueType.connection_type)
+ end
+
context "with a project" do
before do
project.add_developer(current_user)
diff --git a/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb b/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb
index ae3097c1d9e..deb5ff584cf 100644
--- a/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb
+++ b/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb
@@ -14,6 +14,7 @@ RSpec.describe Resolvers::MergeRequestPipelinesResolver do
sha: merge_request.diff_head_sha
)
end
+
let_it_be(:other_project_pipeline) { create(:ci_pipeline, project: merge_request.source_project, ref: 'other-ref') }
let_it_be(:other_pipeline) { create(:ci_pipeline) }
let(:current_user) { create(:user) }
diff --git a/spec/graphql/resolvers/merge_requests_resolver_spec.rb b/spec/graphql/resolvers/merge_requests_resolver_spec.rb
index aecffc487aa..3a3393a185c 100644
--- a/spec/graphql/resolvers/merge_requests_resolver_spec.rb
+++ b/spec/graphql/resolvers/merge_requests_resolver_spec.rb
@@ -34,13 +34,13 @@ RSpec.describe Resolvers::MergeRequestsResolver do
context 'no arguments' do
it 'returns all merge requests' do
- result = resolve_mr(project, {})
+ 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, merge_request_with_milestone)
end
it 'returns only merge requests that the current user can see' do
- result = resolve_mr(project, {}, user: build(:user))
+ result = resolve_mr(project, user: build(:user))
expect(result).to be_empty
end
@@ -236,10 +236,10 @@ RSpec.describe Resolvers::MergeRequestsResolver do
end
def resolve_mr_single(project, iid)
- resolve_mr(project, { iids: iid }, resolver: described_class.single)
+ resolve_mr(project, resolver: described_class.single, iids: iid)
end
- def resolve_mr(project, args, resolver: described_class, user: current_user)
+ def resolve_mr(project, resolver: described_class, user: current_user, **args)
resolve(resolver, obj: project, args: args, ctx: { current_user: user })
end
end
diff --git a/spec/graphql/resolvers/metadata_resolver_spec.rb b/spec/graphql/resolvers/metadata_resolver_spec.rb
index 20556941de4..f8c01f9d531 100644
--- a/spec/graphql/resolvers/metadata_resolver_spec.rb
+++ b/spec/graphql/resolvers/metadata_resolver_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Resolvers::MetadataResolver do
describe '#resolve' do
it 'returns version and revision' do
- expect(resolve(described_class)).to eq(version: Gitlab::VERSION, revision: Gitlab.revision)
+ expect(resolve(described_class)).to have_attributes(version: Gitlab::VERSION, revision: Gitlab.revision)
end
end
end
diff --git a/spec/graphql/resolvers/project_pipeline_resolver_spec.rb b/spec/graphql/resolvers/project_pipeline_resolver_spec.rb
index a6a86c49373..1950c2ca067 100644
--- a/spec/graphql/resolvers/project_pipeline_resolver_spec.rb
+++ b/spec/graphql/resolvers/project_pipeline_resolver_spec.rb
@@ -10,6 +10,10 @@ RSpec.describe Resolvers::ProjectPipelineResolver do
let_it_be(:other_pipeline) { create(:ci_pipeline) }
let(:current_user) { create(:user) }
+ specify do
+ expect(described_class).to have_nullable_graphql_type(::Types::Ci::PipelineType)
+ end
+
def resolve_pipeline(project, args)
resolve(described_class, obj: project, args: args, ctx: { current_user: current_user })
end
diff --git a/spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb b/spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb
index 0775c1c31d1..ad59cb6b95e 100644
--- a/spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb
+++ b/spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb
@@ -5,6 +5,10 @@ require 'spec_helper'
RSpec.describe Resolvers::Projects::JiraImportsResolver do
include GraphqlHelpers
+ specify do
+ expect(described_class).to have_nullable_graphql_type(Types::JiraImportType.connection_type)
+ end
+
describe '#resolve' do
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project, :public) }
diff --git a/spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb b/spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb
index 840aea8b8c4..c375345250d 100644
--- a/spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb
+++ b/spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb
@@ -5,6 +5,10 @@ require 'spec_helper'
RSpec.describe Resolvers::Projects::JiraProjectsResolver do
include GraphqlHelpers
+ specify do
+ expect(described_class).to have_nullable_graphql_type(Types::Projects::Services::JiraProjectType.connection_type)
+ end
+
describe '#resolve' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
diff --git a/spec/graphql/resolvers/projects/services_resolver_spec.rb b/spec/graphql/resolvers/projects/services_resolver_spec.rb
index 8b6eff9e8b6..a1b631113b2 100644
--- a/spec/graphql/resolvers/projects/services_resolver_spec.rb
+++ b/spec/graphql/resolvers/projects/services_resolver_spec.rb
@@ -5,6 +5,10 @@ require 'spec_helper'
RSpec.describe Resolvers::Projects::ServicesResolver do
include GraphqlHelpers
+ specify do
+ expect(described_class).to have_nullable_graphql_type(Types::Projects::ServiceType.connection_type)
+ end
+
describe '#resolve' do
let_it_be(:user) { create(:user) }
diff --git a/spec/graphql/resolvers/projects/snippets_resolver_spec.rb b/spec/graphql/resolvers/projects/snippets_resolver_spec.rb
index b4a5eb8ddb0..6f7feff8fe5 100644
--- a/spec/graphql/resolvers/projects/snippets_resolver_spec.rb
+++ b/spec/graphql/resolvers/projects/snippets_resolver_spec.rb
@@ -56,12 +56,6 @@ RSpec.describe Resolvers::Projects::SnippetsResolver do
expect(snippets).to contain_exactly(project_snippet, other_project_snippet)
end
-
- it 'returns an error if the gid is invalid' do
- expect do
- resolve_snippets(args: { ids: 'foo' })
- end.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
- end
end
context 'when no project is provided' do
diff --git a/spec/graphql/resolvers/projects_resolver_spec.rb b/spec/graphql/resolvers/projects_resolver_spec.rb
index 83a26062957..3de54c7e410 100644
--- a/spec/graphql/resolvers/projects_resolver_spec.rb
+++ b/spec/graphql/resolvers/projects_resolver_spec.rb
@@ -134,8 +134,8 @@ RSpec.describe Resolvers::ProjectsResolver do
is_expected.to eq([named_project3, named_project1, named_project2])
end
- it 'returns projects not in order of similarity to search if flag is off' do
- is_expected.not_to eq([named_project3, named_project1, named_project2])
+ it 'returns projects in any order if flag is off' do
+ is_expected.to match_array([named_project3, named_project1, named_project2])
end
end
end
diff --git a/spec/graphql/resolvers/release_resolver_spec.rb b/spec/graphql/resolvers/release_resolver_spec.rb
index 666d54fbc3c..04765fc68e9 100644
--- a/spec/graphql/resolvers/release_resolver_spec.rb
+++ b/spec/graphql/resolvers/release_resolver_spec.rb
@@ -36,7 +36,7 @@ RSpec.describe Resolvers::ReleaseResolver do
let(:args) { {} }
it 'raises an error' do
- expect { resolve_release }.to raise_error(ArgumentError, "missing keyword: tag_name")
+ expect { resolve_release }.to raise_error(ArgumentError, "missing keyword: :tag_name")
end
end
end
diff --git a/spec/graphql/resolvers/releases_resolver_spec.rb b/spec/graphql/resolvers/releases_resolver_spec.rb
index ee8b33fc748..b9b90686aa7 100644
--- a/spec/graphql/resolvers/releases_resolver_spec.rb
+++ b/spec/graphql/resolvers/releases_resolver_spec.rb
@@ -5,12 +5,19 @@ require 'spec_helper'
RSpec.describe Resolvers::ReleasesResolver do
include GraphqlHelpers
+ let_it_be(:today) { Time.now }
+ let_it_be(:yesterday) { today - 1.day }
+ let_it_be(:tomorrow) { today + 1.day }
+
let_it_be(:project) { create(:project, :private) }
- let_it_be(:release_v1) { create(:release, project: project, tag: 'v1.0.0') }
- let_it_be(:release_v2) { create(:release, project: project, tag: 'v2.0.0') }
+ let_it_be(:release_v1) { create(:release, project: project, tag: 'v1.0.0', released_at: yesterday, created_at: tomorrow) }
+ let_it_be(:release_v2) { create(:release, project: project, tag: 'v2.0.0', released_at: today, created_at: yesterday) }
+ let_it_be(:release_v3) { create(:release, project: project, tag: 'v3.0.0', released_at: tomorrow, created_at: today) }
let_it_be(:developer) { create(:user) }
let_it_be(:public_user) { create(:user) }
+ let(:args) { { sort: :released_at_desc } }
+
before do
project.add_developer(developer)
end
@@ -28,7 +35,41 @@ RSpec.describe Resolvers::ReleasesResolver do
let(:current_user) { developer }
it 'returns all releases associated to the project' do
- expect(resolve_releases).to eq([release_v1, release_v2])
+ expect(resolve_releases).to eq([release_v3, release_v2, release_v1])
+ end
+
+ describe 'sorting behavior' do
+ context 'with sort: :released_at_desc' do
+ let(:args) { { sort: :released_at_desc } }
+
+ it 'returns the releases ordered by released_at in descending order' do
+ expect(resolve_releases).to eq([release_v3, release_v2, release_v1])
+ end
+ end
+
+ context 'with sort: :released_at_asc' do
+ let(:args) { { sort: :released_at_asc } }
+
+ it 'returns the releases ordered by released_at in ascending order' do
+ expect(resolve_releases).to eq([release_v1, release_v2, release_v3])
+ end
+ end
+
+ context 'with sort: :created_desc' do
+ let(:args) { { sort: :created_desc } }
+
+ it 'returns the releases ordered by created_at in descending order' do
+ expect(resolve_releases).to eq([release_v1, release_v3, release_v2])
+ end
+ end
+
+ context 'with sort: :created_asc' do
+ let(:args) { { sort: :created_asc } }
+
+ it 'returns the releases ordered by created_at in ascending order' do
+ expect(resolve_releases).to eq([release_v2, release_v3, release_v1])
+ end
+ end
end
end
end
@@ -37,6 +78,6 @@ RSpec.describe Resolvers::ReleasesResolver do
def resolve_releases
context = { current_user: current_user }
- resolve(described_class, obj: project, args: {}, ctx: context)
+ resolve(described_class, obj: project, args: args, ctx: context)
end
end
diff --git a/spec/graphql/resolvers/snippets/blobs_resolver_spec.rb b/spec/graphql/resolvers/snippets/blobs_resolver_spec.rb
index fdbd87c32be..16e69f662c0 100644
--- a/spec/graphql/resolvers/snippets/blobs_resolver_spec.rb
+++ b/spec/graphql/resolvers/snippets/blobs_resolver_spec.rb
@@ -5,6 +5,10 @@ require 'spec_helper'
RSpec.describe Resolvers::Snippets::BlobsResolver do
include GraphqlHelpers
+ specify do
+ expect(described_class).to have_nullable_graphql_type(Types::Snippets::BlobType.connection_type)
+ end
+
describe '#resolve' do
let_it_be(:current_user) { create(:user) }
let_it_be(:snippet) { create(:personal_snippet, :private, :repository, author: current_user) }
diff --git a/spec/graphql/resolvers/snippets_resolver_spec.rb b/spec/graphql/resolvers/snippets_resolver_spec.rb
index 180be8e8624..a58d9c5ac3a 100644
--- a/spec/graphql/resolvers/snippets_resolver_spec.rb
+++ b/spec/graphql/resolvers/snippets_resolver_spec.rb
@@ -36,7 +36,7 @@ RSpec.describe Resolvers::SnippetsResolver do
context 'when using filters' do
context 'by author id' do
it 'returns the snippets' do
- snippets = resolve_snippets(args: { author_id: current_user.to_global_id })
+ snippets = resolve_snippets(args: { author_id: global_id_of(current_user) })
expect(snippets).to contain_exactly(personal_snippet, project_snippet)
end
@@ -44,7 +44,7 @@ RSpec.describe Resolvers::SnippetsResolver do
it 'returns an error if the param id is invalid' do
expect do
resolve_snippets(args: { author_id: 'foo' })
- end.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
+ end.to raise_error(GraphQL::CoercionError)
end
end
@@ -65,7 +65,7 @@ RSpec.describe Resolvers::SnippetsResolver do
it 'returns an error if the param id is invalid' do
expect do
resolve_snippets(args: { project_id: 'foo' })
- end.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
+ end.to raise_error(GraphQL::CoercionError)
end
end
@@ -99,14 +99,14 @@ RSpec.describe Resolvers::SnippetsResolver do
expect(snippets).to contain_exactly(personal_snippet, project_snippet)
end
- it 'returns an error if the gid is invalid' do
+ it 'returns an error if the id cannot be coerced' do
args = {
ids: [personal_snippet.to_global_id, 'foo']
}
expect do
resolve_snippets(args: args)
- end.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
+ end.to raise_error(GraphQL::CoercionError, '"foo" is not a valid Global ID')
end
it 'returns an error if both project and author are provided' do
diff --git a/spec/graphql/resolvers/todo_resolver_spec.rb b/spec/graphql/resolvers/todo_resolver_spec.rb
index 83e3140b676..c764f389c16 100644
--- a/spec/graphql/resolvers/todo_resolver_spec.rb
+++ b/spec/graphql/resolvers/todo_resolver_spec.rb
@@ -5,6 +5,10 @@ require 'spec_helper'
RSpec.describe Resolvers::TodoResolver do
include GraphqlHelpers
+ specify do
+ expect(described_class).to have_nullable_graphql_type(Types::TodoType.connection_type)
+ end
+
describe '#resolve' do
let_it_be(:current_user) { create(:user) }
let_it_be(:author1) { create(:user) }
diff --git a/spec/graphql/resolvers/tree_resolver_spec.rb b/spec/graphql/resolvers/tree_resolver_spec.rb
index 7818c25fe47..9eafd272771 100644
--- a/spec/graphql/resolvers/tree_resolver_spec.rb
+++ b/spec/graphql/resolvers/tree_resolver_spec.rb
@@ -7,6 +7,10 @@ RSpec.describe Resolvers::TreeResolver do
let(:repository) { create(:project, :repository).repository }
+ specify do
+ expect(described_class).to have_nullable_graphql_type(Types::Tree::TreeType)
+ end
+
describe '#resolve' do
it 'resolves to a tree' do
result = resolve_repository({ ref: "master" })
diff --git a/spec/graphql/resolvers/users/group_count_resolver_spec.rb b/spec/graphql/resolvers/users/group_count_resolver_spec.rb
new file mode 100644
index 00000000000..47160a33646
--- /dev/null
+++ b/spec/graphql/resolvers/users/group_count_resolver_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Resolvers::Users::GroupCountResolver do
+ include GraphqlHelpers
+
+ describe '#resolve' do
+ let_it_be(:user1) { create(:user) }
+ let_it_be(:user2) { create(:user) }
+ let_it_be(:group1) { create(:group) }
+ let_it_be(:group2) { create(:group) }
+ let_it_be(:project) { create(:project, group: create(:group)) }
+ let_it_be(:group_member1) { create(:group_member, source: group1, user_id: user1.id, access_level: Gitlab::Access::OWNER) }
+ let_it_be(:project_member1) { create(:project_member, source: project, user_id: user1.id, access_level: Gitlab::Access::DEVELOPER) }
+ let_it_be(:group_member2) { create(:group_member, source: group2, user_id: user2.id, access_level: Gitlab::Access::DEVELOPER) }
+
+ it 'resolves group count for users' do
+ current_user = user1
+
+ result = batch_sync do
+ [user1, user2].map { |user| resolve_group_count(user, current_user) }
+ end
+
+ expect(result).to eq([2, nil])
+ end
+
+ context 'permissions' do
+ context 'when current_user is an admin', :enable_admin_mode do
+ let_it_be(:admin) { create(:admin) }
+
+ it do
+ result = batch_sync do
+ [user1, user2].map { |user| resolve_group_count(user, admin) }
+ end
+
+ expect(result).to eq([2, 1])
+ end
+ end
+
+ context 'when current_user does not have access to the requested resource' do
+ it do
+ result = batch_sync { resolve_group_count(user1, user2) }
+
+ expect(result).to be nil
+ end
+ end
+
+ context 'when current_user does not exist' do
+ it do
+ result = batch_sync { resolve_group_count(user1, nil) }
+
+ expect(result).to be nil
+ end
+ end
+ end
+ end
+
+ def resolve_group_count(user, current_user)
+ resolve(described_class, obj: user, ctx: { current_user: current_user })
+ end
+end
diff --git a/spec/graphql/resolvers/users/snippets_resolver_spec.rb b/spec/graphql/resolvers/users/snippets_resolver_spec.rb
index 497b6b11b46..9ccbebc59e6 100644
--- a/spec/graphql/resolvers/users/snippets_resolver_spec.rb
+++ b/spec/graphql/resolvers/users/snippets_resolver_spec.rb
@@ -73,7 +73,7 @@ RSpec.describe Resolvers::Users::SnippetsResolver do
expect do
resolve_snippets(args: args)
- end.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
+ end.to raise_error(GraphQL::CoercionError)
end
end
end
diff --git a/spec/graphql/resolvers/users_resolver_spec.rb b/spec/graphql/resolvers/users_resolver_spec.rb
index e3d595e0790..1aa24055a89 100644
--- a/spec/graphql/resolvers/users_resolver_spec.rb
+++ b/spec/graphql/resolvers/users_resolver_spec.rb
@@ -5,8 +5,12 @@ require 'spec_helper'
RSpec.describe Resolvers::UsersResolver do
include GraphqlHelpers
- let_it_be(:user1) { create(:user) }
- let_it_be(:user2) { create(:user) }
+ let_it_be(:user1) { create(:user, name: "SomePerson") }
+ let_it_be(:user2) { create(:user, username: "someone123784") }
+
+ specify do
+ expect(described_class).to have_nullable_graphql_type(Types::UserType.connection_type)
+ end
describe '#resolve' do
it 'raises an error when read_users_list is not authorized' do
@@ -43,6 +47,14 @@ RSpec.describe Resolvers::UsersResolver do
).to contain_exactly(user1, user2)
end
end
+
+ context 'when a search term is passed' do
+ it 'returns all users who match', :aggregate_failures do
+ expect(resolve_users(search: "some")).to contain_exactly(user1, user2)
+ expect(resolve_users(search: "123784")).to contain_exactly(user2)
+ expect(resolve_users(search: "someperson")).to contain_exactly(user1)
+ end
+ end
end
def resolve_users(args = {})
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
index 625fb17bbf8..8a7408224a2 100644
--- 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
@@ -6,7 +6,10 @@ RSpec.describe GitlabSchema.types['MeasurementIdentifier'] do
specify { expect(described_class.graphql_name).to eq('MeasurementIdentifier') }
it 'exposes all the existing identifier values' do
- identifiers = Analytics::InstanceStatistics::Measurement.identifiers.keys.map(&:upcase)
+ 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
diff --git a/spec/graphql/types/alert_management/http_integration_type_spec.rb b/spec/graphql/types/alert_management/http_integration_type_spec.rb
new file mode 100644
index 00000000000..a4b64e2e37f
--- /dev/null
+++ b/spec/graphql/types/alert_management/http_integration_type_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['AlertManagementHttpIntegration'] do
+ specify { expect(described_class.graphql_name).to eq('AlertManagementHttpIntegration') }
+
+ specify { expect(described_class).to require_graphql_authorizations(:admin_operations) }
+end
diff --git a/spec/graphql/types/alert_management/integration_type_enum_spec.rb b/spec/graphql/types/alert_management/integration_type_enum_spec.rb
new file mode 100644
index 00000000000..0cdd67cb140
--- /dev/null
+++ b/spec/graphql/types/alert_management/integration_type_enum_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['AlertManagementIntegrationType'] do
+ specify { expect(described_class.graphql_name).to eq('AlertManagementIntegrationType') }
+
+ describe 'statuses' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:name, :value) do
+ 'PROMETHEUS' | :prometheus
+ 'HTTP' | :http
+ end
+
+ with_them do
+ it 'exposes a type with the correct value' do
+ expect(described_class.values[name].value).to eq(value)
+ end
+ end
+ end
+end
diff --git a/spec/graphql/types/alert_management/integration_type_spec.rb b/spec/graphql/types/alert_management/integration_type_spec.rb
new file mode 100644
index 00000000000..5d149e6da6e
--- /dev/null
+++ b/spec/graphql/types/alert_management/integration_type_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['AlertManagementIntegration'] do
+ specify { expect(described_class.graphql_name).to eq('AlertManagementIntegration') }
+
+ it 'exposes the expected fields' do
+ expected_fields = %i[
+ id
+ type
+ name
+ active
+ token
+ url
+ api_url
+ ]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/alert_management/prometheus_integration_type_spec.rb b/spec/graphql/types/alert_management/prometheus_integration_type_spec.rb
new file mode 100644
index 00000000000..0e9994035d8
--- /dev/null
+++ b/spec/graphql/types/alert_management/prometheus_integration_type_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['AlertManagementPrometheusIntegration'] do
+ include GraphqlHelpers
+
+ specify { expect(described_class.graphql_name).to eq('AlertManagementPrometheusIntegration') }
+ specify { expect(described_class).to require_graphql_authorizations(:admin_project) }
+
+ describe 'resolvers' do
+ shared_examples_for 'has field with value' do |field_name|
+ it 'correctly renders the field' do
+ expect(resolve_field(field_name, integration)).to eq(value)
+ end
+ end
+
+ let_it_be_with_reload(:integration) { create(:prometheus_service) }
+
+ it_behaves_like 'has field with value', 'name' do
+ let(:value) { integration.title }
+ end
+
+ it_behaves_like 'has field with value', 'type' do
+ let(:value) { :prometheus }
+ end
+
+ it_behaves_like 'has field with value', 'token' do
+ let(:value) { nil }
+ end
+
+ it_behaves_like 'has field with value', 'url' do
+ let(:value) { "http://localhost/#{integration.project.full_path}/prometheus/alerts/notify.json" }
+ end
+
+ it_behaves_like 'has field with value', 'active' do
+ let(:value) { integration.manual_configuration? }
+ end
+
+ context 'with alerting setting' do
+ let_it_be(:alerting_setting) { create(:project_alerting_setting, project: integration.project) }
+
+ it_behaves_like 'has field with value', 'token' do
+ let(:value) { alerting_setting.token }
+ end
+ end
+
+ context 'without project' do
+ let_it_be(:integration) { create(:prometheus_service, project: nil, group: create(:group)) }
+
+ it_behaves_like 'has field with value', 'token' do
+ let(:value) { nil }
+ end
+
+ it_behaves_like 'has field with value', 'url' do
+ let(:value) { nil }
+ end
+ end
+ end
+end
diff --git a/spec/graphql/types/availability_enum_spec.rb b/spec/graphql/types/availability_enum_spec.rb
new file mode 100644
index 00000000000..a9bdf5e4da6
--- /dev/null
+++ b/spec/graphql/types/availability_enum_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['AvailabilityEnum'] do
+ specify { expect(described_class.graphql_name).to eq('AvailabilityEnum') }
+
+ it 'exposes all the existing access levels' do
+ expect(described_class.values.keys).to match_array(%w[NOT_SET BUSY])
+ end
+end
diff --git a/spec/graphql/types/ci/detailed_status_type_spec.rb b/spec/graphql/types/ci/detailed_status_type_spec.rb
index ddb3a1450df..9fa3280657a 100644
--- a/spec/graphql/types/ci/detailed_status_type_spec.rb
+++ b/spec/graphql/types/ci/detailed_status_type_spec.rb
@@ -3,11 +3,30 @@
require 'spec_helper'
RSpec.describe Types::Ci::DetailedStatusType do
+ include GraphqlHelpers
+
specify { expect(described_class.graphql_name).to eq('DetailedStatus') }
- it "has all fields" do
+ it 'has all fields' do
expect(described_class).to have_graphql_fields(:group, :icon, :favicon,
:details_path, :has_details,
:label, :text, :tooltip, :action)
end
+
+ describe 'action field' do
+ it 'correctly renders the field' do
+ stage = create(:ci_stage_entity, status: :skipped)
+ status = stage.detailed_status(stage.pipeline.user)
+
+ expected_status = {
+ button_title: status.action_button_title,
+ icon: status.action_icon,
+ method: status.action_method,
+ path: status.action_path,
+ title: status.action_title
+ }
+
+ expect(resolve_field('action', status)).to eq(expected_status)
+ end
+ end
end
diff --git a/spec/graphql/types/ci/job_type_spec.rb b/spec/graphql/types/ci/job_type_spec.rb
index 3a54ed2efed..3dcb81eefbf 100644
--- a/spec/graphql/types/ci/job_type_spec.rb
+++ b/spec/graphql/types/ci/job_type_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe Types::Ci::JobType do
it 'exposes the expected fields' do
expected_fields = %i[
+ pipeline
name
needs
detailedStatus
diff --git a/spec/graphql/types/ci/runner_setup_type_spec.rb b/spec/graphql/types/ci/runner_setup_type_spec.rb
new file mode 100644
index 00000000000..197e717e964
--- /dev/null
+++ b/spec/graphql/types/ci/runner_setup_type_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::Ci::RunnerSetupType do
+ specify { expect(described_class.graphql_name).to eq('RunnerSetup') }
+
+ it 'exposes the expected fields' do
+ expected_fields = %i[
+ install_instructions
+ register_instructions
+ ]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/commit_type_spec.rb b/spec/graphql/types/commit_type_spec.rb
index d222287270d..e9bc7f6bb94 100644
--- a/spec/graphql/types/commit_type_spec.rb
+++ b/spec/graphql/types/commit_type_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe GitlabSchema.types['Commit'] do
it 'contains attributes related to commit' do
expect(described_class).to have_graphql_fields(
:id, :sha, :title, :description, :description_html, :message, :title_html, :authored_date,
- :author_name, :author_gravatar, :author, :web_url, :web_path, :latest_pipeline,
+ :author_name, :author_gravatar, :author, :web_url, :web_path,
:pipelines, :signature_html
)
end
diff --git a/spec/graphql/types/container_repository_cleanup_status_enum_spec.rb b/spec/graphql/types/container_repository_cleanup_status_enum_spec.rb
new file mode 100644
index 00000000000..36cfc789ee9
--- /dev/null
+++ b/spec/graphql/types/container_repository_cleanup_status_enum_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['ContainerRepositoryCleanupStatus'] do
+ it 'exposes all statuses' do
+ expected_keys = ContainerRepository.expiration_policy_cleanup_statuses
+ .keys
+ .map { |k| k.gsub('cleanup_', '') }
+ .map(&:upcase)
+ expect(described_class.values.keys).to contain_exactly(*expected_keys)
+ end
+end
diff --git a/spec/graphql/types/container_repository_details_type_spec.rb b/spec/graphql/types/container_repository_details_type_spec.rb
new file mode 100644
index 00000000000..b5ff460fcf7
--- /dev/null
+++ b/spec/graphql/types/container_repository_details_type_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['ContainerRepositoryDetails'] do
+ fields = %i[id name path location created_at updated_at expiration_policy_started_at status tags_count can_delete expiration_policy_cleanup_status tags]
+
+ it { expect(described_class.graphql_name).to eq('ContainerRepositoryDetails') }
+
+ it { expect(described_class.description).to eq('Details of a container repository') }
+
+ it { expect(described_class).to require_graphql_authorizations(:read_container_image) }
+
+ it { expect(described_class).to have_graphql_fields(fields) }
+
+ describe 'tags field' do
+ subject { described_class.fields['tags'] }
+
+ it 'returns tags connection type' do
+ is_expected.to have_graphql_type(Types::ContainerRepositoryTagType.connection_type)
+ end
+ end
+end
diff --git a/spec/graphql/types/container_repository_status_enum_spec.rb b/spec/graphql/types/container_repository_status_enum_spec.rb
new file mode 100644
index 00000000000..9598879779a
--- /dev/null
+++ b/spec/graphql/types/container_repository_status_enum_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['ContainerRepositoryStatus'] do
+ it 'exposes all statuses' do
+ expect(described_class.values.keys).to contain_exactly(*ContainerRepository.statuses.keys.map(&:upcase))
+ end
+end
diff --git a/spec/graphql/types/container_repository_tag_type_spec.rb b/spec/graphql/types/container_repository_tag_type_spec.rb
new file mode 100644
index 00000000000..1d1a76d6916
--- /dev/null
+++ b/spec/graphql/types/container_repository_tag_type_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['ContainerRepositoryTag'] do
+ fields = %i[name path location digest revision short_revision total_size created_at can_delete]
+
+ it { expect(described_class.graphql_name).to eq('ContainerRepositoryTag') }
+
+ it { expect(described_class.description).to eq('A tag from a container repository') }
+
+ it { expect(described_class).to require_graphql_authorizations(:read_container_image) }
+
+ it { expect(described_class).to have_graphql_fields(fields) }
+end
diff --git a/spec/graphql/types/container_repository_type_spec.rb b/spec/graphql/types/container_repository_type_spec.rb
new file mode 100644
index 00000000000..3d3445ba5c3
--- /dev/null
+++ b/spec/graphql/types/container_repository_type_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['ContainerRepository'] do
+ fields = %i[id name path location created_at updated_at expiration_policy_started_at status tags_count can_delete expiration_policy_cleanup_status]
+
+ it { expect(described_class.graphql_name).to eq('ContainerRepository') }
+
+ it { expect(described_class.description).to eq('A container repository') }
+
+ it { expect(described_class).to require_graphql_authorizations(:read_container_image) }
+
+ it { expect(described_class).to have_graphql_fields(fields) }
+
+ describe 'status field' do
+ subject { described_class.fields['status'] }
+
+ it 'returns status enum' do
+ is_expected.to have_graphql_type(Types::ContainerRepositoryStatusEnum)
+ end
+ end
+
+ describe 'expiration_policy_cleanup_status field' do
+ subject { described_class.fields['expirationPolicyCleanupStatus'] }
+
+ it 'returns cleanup status enum' do
+ is_expected.to have_graphql_type(Types::ContainerRepositoryCleanupStatusEnum)
+ end
+ end
+end
diff --git a/spec/graphql/types/countable_connection_type_spec.rb b/spec/graphql/types/countable_connection_type_spec.rb
index af34611ecfe..3b3c02baa5d 100644
--- a/spec/graphql/types/countable_connection_type_spec.rb
+++ b/spec/graphql/types/countable_connection_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe GitlabSchema.types['IssueConnection'] do
+RSpec.describe GitlabSchema.types['MergeRequestConnection'] do
it 'has the expected fields' do
expected_fields = %i[count page_info edges nodes]
diff --git a/spec/graphql/types/custom_emoji_type_spec.rb b/spec/graphql/types/custom_emoji_type_spec.rb
new file mode 100644
index 00000000000..7f3c99e4b63
--- /dev/null
+++ b/spec/graphql/types/custom_emoji_type_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['CustomEmoji'] do
+ specify { expect(described_class.graphql_name).to eq('CustomEmoji') }
+
+ specify { expect(described_class).to require_graphql_authorizations(:read_custom_emoji) }
+
+ specify { expect(described_class).to have_graphql_fields(:id, :name, :url, :external) }
+end
diff --git a/spec/graphql/types/environment_type_spec.rb b/spec/graphql/types/environment_type_spec.rb
index 2220f847e4e..3671d35e8a5 100644
--- a/spec/graphql/types/environment_type_spec.rb
+++ b/spec/graphql/types/environment_type_spec.rb
@@ -44,18 +44,12 @@ RSpec.describe GitlabSchema.types['Environment'] do
expect(subject['data']['project']['environment']['name']).to eq(environment.name)
end
- it 'returns the path when the feature is enabled' do
+ it 'returns the path to the environment' do
expect(subject['data']['project']['environment']['path']).to eq(
Gitlab::Routing.url_helpers.project_environment_path(project, environment)
)
end
- it 'does not return the path when the feature is disabled' do
- stub_feature_flags(expose_environment_path_in_alert_details: false)
-
- expect(subject['data']['project']['environment']['path']).to be_nil
- end
-
context 'when query alert data for the environment' do
let_it_be(:query) do
%(
diff --git a/spec/graphql/types/global_id_type_spec.rb b/spec/graphql/types/global_id_type_spec.rb
index 7589b0e285e..cb129868f7e 100644
--- a/spec/graphql/types/global_id_type_spec.rb
+++ b/spec/graphql/types/global_id_type_spec.rb
@@ -45,8 +45,7 @@ RSpec.describe Types::GlobalIDType do
end
it 'rejects nil' do
- expect { described_class.coerce_isolated_input(nil) }
- .to raise_error(GraphQL::CoercionError)
+ expect(described_class.coerce_isolated_input(nil)).to be_nil
end
it 'rejects gids from different apps' do
diff --git a/spec/graphql/types/grafana_integration_type_spec.rb b/spec/graphql/types/grafana_integration_type_spec.rb
index b4658db08d7..816264c36c8 100644
--- a/spec/graphql/types/grafana_integration_type_spec.rb
+++ b/spec/graphql/types/grafana_integration_type_spec.rb
@@ -7,7 +7,6 @@ RSpec.describe GitlabSchema.types['GrafanaIntegration'] do
%i[
id
grafana_url
- token
enabled
created_at
updated_at
diff --git a/spec/graphql/types/group_invitation_type_spec.rb b/spec/graphql/types/group_invitation_type_spec.rb
new file mode 100644
index 00000000000..dab2d43fc90
--- /dev/null
+++ b/spec/graphql/types/group_invitation_type_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::GroupInvitationType do
+ specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Group) }
+
+ specify { expect(described_class.graphql_name).to eq('GroupInvitation') }
+
+ specify { expect(described_class).to require_graphql_authorizations(:read_group) }
+
+ it 'has the expected fields' do
+ expected_fields = %w[
+ email 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/invitation_interface_spec.rb b/spec/graphql/types/invitation_interface_spec.rb
new file mode 100644
index 00000000000..8f345c58ca3
--- /dev/null
+++ b/spec/graphql/types/invitation_interface_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::InvitationInterface do
+ it 'exposes the expected fields' do
+ expected_fields = %i[
+ email
+ access_level
+ created_by
+ created_at
+ updated_at
+ expires_at
+ user
+ ]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+
+ describe '.resolve_type' do
+ subject { described_class.resolve_type(object, {}) }
+
+ context 'for project member' do
+ let(:object) { build(:project_member) }
+
+ it { is_expected.to be Types::ProjectInvitationType }
+ end
+
+ context 'for group member' do
+ let(:object) { build(:group_member) }
+
+ it { is_expected.to be Types::GroupInvitationType }
+ end
+
+ context 'for an unknown type' do
+ let(:object) { build(:user) }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::BaseError)
+ end
+ end
+ end
+end
diff --git a/spec/graphql/types/issue_type_spec.rb b/spec/graphql/types/issue_type_spec.rb
index c55e624dd11..558fc479af1 100644
--- a/spec/graphql/types/issue_type_spec.rb
+++ b/spec/graphql/types/issue_type_spec.rb
@@ -14,10 +14,10 @@ RSpec.describe GitlabSchema.types['Issue'] do
specify { expect(described_class.interfaces).to include(Types::CurrentUserTodos) }
it 'has specific fields' do
- fields = %i[id iid title description state reference author assignees participants labels milestone due_date
- confidential discussion_locked upvotes downvotes user_notes_count web_path web_url relative_position
- subscribed time_estimate total_time_spent closed_at created_at updated_at task_completion_status
- designs design_collection alert_management_alert severity current_user_todos]
+ fields = %i[id iid title description state reference author assignees updated_by participants labels milestone due_date
+ confidential discussion_locked upvotes downvotes user_notes_count user_discussions_count web_path web_url relative_position
+ emails_disabled subscribed time_estimate total_time_spent human_time_estimate human_total_time_spent closed_at created_at updated_at task_completion_status
+ design_collection alert_management_alert severity current_user_todos moved moved_to]
fields.each do |field_name|
expect(described_class).to have_graphql_field(field_name)
diff --git a/spec/graphql/types/merge_request_type_spec.rb b/spec/graphql/types/merge_request_type_spec.rb
index 9d901655b7b..8800250b103 100644
--- a/spec/graphql/types/merge_request_type_spec.rb
+++ b/spec/graphql/types/merge_request_type_spec.rb
@@ -17,11 +17,11 @@ RSpec.describe GitlabSchema.types['MergeRequest'] do
description_html state created_at updated_at source_project target_project
project project_id source_project_id target_project_id source_branch
target_branch work_in_progress merge_when_pipeline_succeeds diff_head_sha
- merge_commit_sha user_notes_count should_remove_source_branch
+ merge_commit_sha user_notes_count user_discussions_count should_remove_source_branch
diff_refs diff_stats diff_stats_summary
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
+ rebase_in_progress default_merge_commit_message
merge_ongoing mergeable_discussions_state web_url
source_branch_exists target_branch_exists
upvotes downvotes head_pipeline pipelines task_completion_status
diff --git a/spec/graphql/types/permission_types/note_spec.rb b/spec/graphql/types/permission_types/note_spec.rb
index 9769c7b3aa3..d75284951c7 100644
--- a/spec/graphql/types/permission_types/note_spec.rb
+++ b/spec/graphql/types/permission_types/note_spec.rb
@@ -5,9 +5,9 @@ require 'spec_helper'
RSpec.describe GitlabSchema.types['NotePermissions'] do
it 'has the expected fields' do
expected_permissions = [
- :read_note, :create_note, :admin_note, :resolve_note, :award_emoji
+ :read_note, :create_note, :admin_note, :resolve_note, :reposition_note, :award_emoji
]
- expect(described_class).to have_graphql_fields(expected_permissions)
+ expect(described_class).to have_graphql_fields(expected_permissions).only
end
end
diff --git a/spec/graphql/types/project_invitation_type_spec.rb b/spec/graphql/types/project_invitation_type_spec.rb
new file mode 100644
index 00000000000..148a763a5fa
--- /dev/null
+++ b/spec/graphql/types/project_invitation_type_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::ProjectInvitationType do
+ specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Project) }
+
+ specify { expect(described_class.graphql_name).to eq('ProjectInvitation') }
+
+ 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_statistics_type_spec.rb b/spec/graphql/types/project_statistics_type_spec.rb
index e6cffd407de..407ce82e73a 100644
--- a/spec/graphql/types/project_statistics_type_spec.rb
+++ b/spec/graphql/types/project_statistics_type_spec.rb
@@ -6,6 +6,6 @@ RSpec.describe GitlabSchema.types['ProjectStatistics'] do
it 'has all the required fields' do
expect(described_class).to have_graphql_fields(:storage_size, :repository_size, :lfs_objects_size,
:build_artifacts_size, :packages_size, :commit_count,
- :wiki_size, :snippets_size)
+ :wiki_size, :snippets_size, :uploads_size)
end
end
diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb
index 8aa9e1138cc..be579e92fb3 100644
--- a/spec/graphql/types/project_type_spec.rb
+++ b/spec/graphql/types/project_type_spec.rb
@@ -27,7 +27,8 @@ RSpec.describe GitlabSchema.types['Project'] do
environment boards jira_import_status jira_imports services releases release
alert_management_alerts alert_management_alert alert_management_alert_status_counts
container_expiration_policy service_desk_enabled service_desk_address
- issue_status_counts terraform_states
+ issue_status_counts terraform_states alert_management_integrations
+
]
expect(described_class).to include_graphql_fields(*expected_fields)
diff --git a/spec/graphql/types/projects/services_enum_spec.rb b/spec/graphql/types/projects/services_enum_spec.rb
index dac1213daf3..b8da9305de4 100644
--- a/spec/graphql/types/projects/services_enum_spec.rb
+++ b/spec/graphql/types/projects/services_enum_spec.rb
@@ -11,5 +11,5 @@ RSpec.describe GitlabSchema.types['ServiceType'] do
end
def available_services_enum
- ::Service.services_types.map(&:underscore).map(&:upcase)
+ ::Service.available_services_types(include_dev: false).map(&:underscore).map(&:upcase)
end
diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb
index 1d9ca8323f8..7a0b3035607 100644
--- a/spec/graphql/types/query_type_spec.rb
+++ b/spec/graphql/types/query_type_spec.rb
@@ -80,4 +80,18 @@ RSpec.describe GitlabSchema.types['Query'] do
is_expected.to have_graphql_type(Types::Ci::RunnerPlatformType.connection_type)
end
end
+
+ describe 'runner_setup field' do
+ subject { described_class.fields['runnerSetup'] }
+
+ it 'returns runner setup instructions' do
+ is_expected.to have_graphql_type(Types::Ci::RunnerSetupType)
+ end
+ end
+
+ describe 'container_repository field' do
+ subject { described_class.fields['containerRepository'] }
+
+ it { is_expected.to have_graphql_type(Types::ContainerRepositoryDetailsType) }
+ end
end
diff --git a/spec/graphql/types/release_asset_link_input_type_spec.rb b/spec/graphql/types/release_asset_link_input_type_spec.rb
new file mode 100644
index 00000000000..d97a91b609a
--- /dev/null
+++ b/spec/graphql/types/release_asset_link_input_type_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::ReleaseAssetLinkInputType do
+ specify { expect(described_class.graphql_name).to eq('ReleaseAssetLinkInput') }
+
+ it 'has the correct arguments' do
+ expect(described_class.arguments.keys).to match_array(%w[name url directAssetPath linkType])
+ end
+
+ it 'sets the type of link_type argument to ReleaseAssetLinkTypeEnum' do
+ expect(described_class.arguments['linkType'].type).to eq(Types::ReleaseAssetLinkTypeEnum)
+ end
+end
diff --git a/spec/graphql/types/release_assets_input_type_spec.rb b/spec/graphql/types/release_assets_input_type_spec.rb
new file mode 100644
index 00000000000..c44abe1e171
--- /dev/null
+++ b/spec/graphql/types/release_assets_input_type_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::ReleaseAssetsInputType do
+ specify { expect(described_class.graphql_name).to eq('ReleaseAssetsInput') }
+
+ it 'has the correct arguments' do
+ expect(described_class.arguments.keys).to match_array(%w[links])
+ end
+
+ it 'sets the type of links argument to ReleaseAssetLinkInputType' do
+ expect(described_class.arguments['links'].type.of_type.of_type).to eq(Types::ReleaseAssetLinkInputType)
+ end
+end
diff --git a/spec/graphql/types/release_links_type_spec.rb b/spec/graphql/types/release_links_type_spec.rb
index d505f0a4b5c..38c38d58baa 100644
--- a/spec/graphql/types/release_links_type_spec.rb
+++ b/spec/graphql/types/release_links_type_spec.rb
@@ -8,8 +8,11 @@ RSpec.describe GitlabSchema.types['ReleaseLinks'] do
it 'has the expected fields' do
expected_fields = %w[
selfUrl
- mergeRequestsUrl
- issuesUrl
+ openedMergeRequestsUrl
+ mergedMergeRequestsUrl
+ closedMergeRequestsUrl
+ openedIssuesUrl
+ closedIssuesUrl
editUrl
]
diff --git a/spec/graphql/types/root_storage_statistics_type_spec.rb b/spec/graphql/types/root_storage_statistics_type_spec.rb
index 79d474f13ad..4fef8f6eafd 100644
--- a/spec/graphql/types/root_storage_statistics_type_spec.rb
+++ b/spec/graphql/types/root_storage_statistics_type_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe GitlabSchema.types['RootStorageStatistics'] do
it 'has all the required fields' do
expect(described_class).to have_graphql_fields(:storage_size, :repository_size, :lfs_objects_size,
:build_artifacts_size, :packages_size, :wiki_size, :snippets_size,
- :pipeline_artifacts_size)
+ :pipeline_artifacts_size, :uploads_size)
end
specify { expect(described_class).to require_graphql_authorizations(:read_statistics) }
diff --git a/spec/graphql/types/security/report_types_enum_spec.rb b/spec/graphql/types/security/report_types_enum_spec.rb
new file mode 100644
index 00000000000..2da852606c7
--- /dev/null
+++ b/spec/graphql/types/security/report_types_enum_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['SecurityReportTypeEnum'] do
+ it 'exposes all security report types' do
+ expect(described_class.values.keys).to contain_exactly(
+ *::Security::SecurityJobsFinder.allowed_job_types.map(&:to_s).map(&:upcase)
+ )
+ end
+end
diff --git a/spec/graphql/types/terraform/state_type_spec.rb b/spec/graphql/types/terraform/state_type_spec.rb
index 51508208046..9f65bb926d7 100644
--- a/spec/graphql/types/terraform/state_type_spec.rb
+++ b/spec/graphql/types/terraform/state_type_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe GitlabSchema.types['TerraformState'] do
it { expect(described_class).to require_graphql_authorizations(:read_terraform_state) }
describe 'fields' do
- let(:fields) { %i[id name locked_by_user locked_at created_at updated_at] }
+ let(:fields) { %i[id name locked_by_user locked_at latest_version created_at updated_at] }
it { expect(described_class).to have_graphql_fields(fields) }
@@ -17,5 +17,8 @@ RSpec.describe GitlabSchema.types['TerraformState'] do
it { expect(described_class.fields['lockedAt'].type).not_to be_non_null }
it { expect(described_class.fields['createdAt'].type).to be_non_null }
it { expect(described_class.fields['updatedAt'].type).to be_non_null }
+
+ it { expect(described_class.fields['latestVersion'].type).not_to be_non_null }
+ it { expect(described_class.fields['latestVersion'].complexity).to eq(3) }
end
end
diff --git a/spec/graphql/types/terraform/state_version_type_spec.rb b/spec/graphql/types/terraform/state_version_type_spec.rb
new file mode 100644
index 00000000000..1c1e95039dc
--- /dev/null
+++ b/spec/graphql/types/terraform/state_version_type_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['TerraformStateVersion'] do
+ it { expect(described_class.graphql_name).to eq('TerraformStateVersion') }
+ it { expect(described_class).to require_graphql_authorizations(:read_terraform_state) }
+
+ describe 'fields' do
+ let(:fields) { %i[id created_by_user job created_at updated_at] }
+
+ it { expect(described_class).to have_graphql_fields(fields) }
+
+ it { expect(described_class.fields['id'].type).to be_non_null }
+ it { expect(described_class.fields['createdByUser'].type).not_to be_non_null }
+ it { expect(described_class.fields['job'].type).not_to be_non_null }
+ it { expect(described_class.fields['createdAt'].type).to be_non_null }
+ it { expect(described_class.fields['updatedAt'].type).to be_non_null }
+ end
+end
diff --git a/spec/graphql/types/user_status_type_spec.rb b/spec/graphql/types/user_status_type_spec.rb
index c4421a9cc10..ced9c40d552 100644
--- a/spec/graphql/types/user_status_type_spec.rb
+++ b/spec/graphql/types/user_status_type_spec.rb
@@ -10,6 +10,7 @@ RSpec.describe Types::UserStatusType do
emoji
message
message_html
+ availability
]
expect(described_class).to have_graphql_fields(*expected_fields)
diff --git a/spec/graphql/types/user_type_spec.rb b/spec/graphql/types/user_type_spec.rb
index 1d5af24b3d9..c8953d9ccb7 100644
--- a/spec/graphql/types/user_type_spec.rb
+++ b/spec/graphql/types/user_type_spec.rb
@@ -24,6 +24,7 @@ RSpec.describe GitlabSchema.types['User'] do
authoredMergeRequests
assignedMergeRequests
groupMemberships
+ groupCount
projectMemberships
starredProjects
]