Commit 454ba65e authored by Vitali Tatarintev's avatar Vitali Tatarintev

Merge branch 'sy-create-integrations-api' into 'master'

Add GraphQL support for creating alert integrations

See merge request gitlab-org/gitlab!45842
parents 078d49af e31c4785
# frozen_string_literal: true
module Mutations
module AlertManagement
module HttpIntegration
class Create < HttpIntegrationBase
include ResolvesProject
graphql_name 'HttpIntegrationCreate'
argument :project_path, GraphQL::ID_TYPE,
required: true,
description: 'The project to create the integration in'
argument :name, GraphQL::STRING_TYPE,
required: true,
description: 'The name of the integration'
argument :active, GraphQL::BOOLEAN_TYPE,
required: true,
description: 'Whether the integration is receiving alerts'
def resolve(args)
project = authorized_find!(full_path: args[:project_path])
response ::AlertManagement::HttpIntegrations::CreateService.new(
project,
current_user,
args.slice(:name, :active)
).execute
end
private
def find_object(full_path:)
resolve_project(full_path: full_path)
end
end
end
end
end
# frozen_string_literal: true
module Mutations
module AlertManagement
module HttpIntegration
class HttpIntegrationBase < BaseMutation
field :integration,
Types::AlertManagement::HttpIntegrationType,
null: true,
description: "The updated HTTP integration"
authorize :admin_operations
private
def find_object(id:)
GitlabSchema.object_from_id(id, expected_class: ::AlertManagement::HttpIntegration)
end
def response(result)
{
integration: result.payload[:integration],
errors: result.errors
}
end
end
end
end
end
# frozen_string_literal: true
module Mutations
module AlertManagement
module HttpIntegration
class ResetToken < HttpIntegrationBase
graphql_name 'HttpIntegrationResetToken'
argument :id, Types::GlobalIDType[::AlertManagement::HttpIntegration],
required: true,
description: "The id of the integration to mutate"
def resolve(id:)
integration = authorized_find!(id: id)
response ::AlertManagement::HttpIntegrations::UpdateService.new(
integration,
current_user,
regenerate_token: true
).execute
end
end
end
end
end
# frozen_string_literal: true
module Mutations
module AlertManagement
module HttpIntegration
class Update < HttpIntegrationBase
graphql_name 'HttpIntegrationUpdate'
argument :id, Types::GlobalIDType[::AlertManagement::HttpIntegration],
required: true,
description: "The id of the integration to mutate"
argument :name, GraphQL::STRING_TYPE,
required: false,
description: "The name of the integration"
argument :active, GraphQL::BOOLEAN_TYPE,
required: false,
description: "Whether the integration is receiving alerts"
def resolve(args)
integration = authorized_find!(id: args[:id])
response ::AlertManagement::HttpIntegrations::UpdateService.new(
integration,
current_user,
args.slice(:name, :active)
).execute
end
end
end
end
end
# frozen_string_literal: true
module Mutations
module AlertManagement
module PrometheusIntegration
class Create < PrometheusIntegrationBase
include ResolvesProject
graphql_name 'PrometheusIntegrationCreate'
argument :project_path, GraphQL::ID_TYPE,
required: true,
description: 'The project to create the integration in'
argument :active, GraphQL::BOOLEAN_TYPE,
required: true,
description: 'Whether the integration is receiving alerts'
argument :api_url, GraphQL::STRING_TYPE,
required: true,
description: 'Endpoint at which prometheus can be queried'
def resolve(args)
project = authorized_find!(full_path: args[:project_path])
return integration_exists if project.prometheus_service
result = ::Projects::Operations::UpdateService.new(
project,
current_user,
**integration_attributes(args),
**token_attributes
).execute
response(project.prometheus_service, result)
end
private
def find_object(full_path:)
resolve_project(full_path: full_path)
end
def integration_exists
response(nil, message: _('Multiple Prometheus integrations are not supported'))
end
end
end
end
end
# frozen_string_literal: true
module Mutations
module AlertManagement
module PrometheusIntegration
class PrometheusIntegrationBase < BaseMutation
field :integration,
Types::AlertManagement::PrometheusIntegrationType,
null: true,
description: "The newly created integration"
authorize :admin_project
private
def find_object(id:)
GitlabSchema.object_from_id(id, expected_class: ::PrometheusService)
end
def response(integration, result)
{
integration: integration,
errors: Array(result[:message])
}
end
def integration_attributes(args)
{
prometheus_integration_attributes: {
manual_configuration: args[:active],
api_url: args[:api_url]
}.compact
}
end
def token_attributes
{ alerting_setting_attributes: { regenerate_token: true } }
end
end
end
end
end
# frozen_string_literal: true
module Mutations
module AlertManagement
module PrometheusIntegration
class ResetToken < PrometheusIntegrationBase
graphql_name 'PrometheusIntegrationResetToken'
argument :id, Types::GlobalIDType[::PrometheusService],
required: true,
description: "The id of the integration to mutate"
def resolve(id:)
integration = authorized_find!(id: id)
result = ::Projects::Operations::UpdateService.new(
integration.project,
current_user,
token_attributes
).execute
response integration, result
end
end
end
end
end
# frozen_string_literal: true
module Mutations
module AlertManagement
module PrometheusIntegration
class Update < PrometheusIntegrationBase
graphql_name 'PrometheusIntegrationUpdate'
argument :id, Types::GlobalIDType[::PrometheusService],
required: true,
description: "The id of the integration to mutate"
argument :active, GraphQL::BOOLEAN_TYPE,
required: false,
description: "Whether the integration is receiving alerts"
argument :api_url, GraphQL::STRING_TYPE,
required: false,
description: "Endpoint at which prometheus can be queried"
def resolve(args)
integration = authorized_find!(id: args[:id])
result = ::Projects::Operations::UpdateService.new(
integration.project,
current_user,
integration_attributes(args)
).execute
response integration.reset, result
end
end
end
end
end
......@@ -11,6 +11,12 @@ module Types
mount_mutation Mutations::AlertManagement::UpdateAlertStatus
mount_mutation Mutations::AlertManagement::Alerts::SetAssignees
mount_mutation Mutations::AlertManagement::Alerts::Todo::Create
mount_mutation Mutations::AlertManagement::HttpIntegration::Create
mount_mutation Mutations::AlertManagement::HttpIntegration::Update
mount_mutation Mutations::AlertManagement::HttpIntegration::ResetToken
mount_mutation Mutations::AlertManagement::PrometheusIntegration::Create
mount_mutation Mutations::AlertManagement::PrometheusIntegration::Update
mount_mutation Mutations::AlertManagement::PrometheusIntegration::ResetToken
mount_mutation Mutations::AwardEmojis::Add
mount_mutation Mutations::AwardEmojis::Remove
mount_mutation Mutations::AwardEmojis::Toggle
......
# frozen_string_literal: true
module AlertManagement
module HttpIntegrations
class CreateService
# @param project [Project]
# @param current_user [User]
# @param params [Hash]
def initialize(project, current_user, params)
@project = project
@current_user = current_user
@params = params
end
def execute
return error_no_permissions unless allowed?
return error_multiple_integrations unless creation_allowed? && Feature.enabled?(:multiple_http_integrations, project)
integration = project.alert_management_http_integrations.create(params)
return error_in_create(integration) unless integration.valid?
success(integration)
end
private
attr_reader :project, :current_user, :params
def allowed?
current_user&.can?(:admin_operations, project)
end
def creation_allowed?
project.alert_management_http_integrations.empty?
end
def error(message)
ServiceResponse.error(message: message)
end
def success(integration)
ServiceResponse.success(payload: { integration: integration })
end
def error_no_permissions
error(_('You have insufficient permissions to create an HTTP integration for this project'))
end
def error_multiple_integrations
error(_('Multiple HTTP integrations are not supported for this project'))
end
def error_in_create(integration)
error(integration.errors.full_messages.to_sentence)
end
end
end
end
::AlertManagement::HttpIntegrations::CreateService.prepend_if_ee('::EE::AlertManagement::HttpIntegrations::CreateService')
# frozen_string_literal: true
module AlertManagement
module HttpIntegrations
class UpdateService
# @param integration [AlertManagement::HttpIntegration]
# @param current_user [User]
# @param params [Hash]
def initialize(integration, current_user, params)
@integration = integration
@current_user = current_user
@params = params
end
def execute
return error_no_permissions unless allowed?
return error_multiple_integrations unless Feature.enabled?(:multiple_http_integrations, integration.project)
params[:token] = nil if params.delete(:regenerate_token)
if integration.update(params)
success
else
error(integration.errors.full_messages.to_sentence)
end
end
private
attr_reader :integration, :current_user, :params
def allowed?
current_user&.can?(:admin_operations, integration)
end
def error(message)
ServiceResponse.error(message: message)
end
def success
ServiceResponse.success(payload: { integration: integration.reset })
end
def error_no_permissions
error(_('You have insufficient permissions to update this HTTP integration'))
end
def error_multiple_integrations
error(_('Multiple HTTP integrations are not supported for this project'))
end
end
end
end
......@@ -630,6 +630,11 @@ type AlertManagementHttpIntegration implements AlertManagementIntegration {
url: String
}
"""
Identifier of AlertManagement::HttpIntegration
"""
scalar AlertManagementHttpIntegrationID
interface AlertManagementIntegration {
"""
Whether the endpoint is currently accepting alerts
......@@ -9226,6 +9231,131 @@ enum HealthStatus {
onTrack
}
"""
Autogenerated input type of HttpIntegrationCreate
"""
input HttpIntegrationCreateInput {
"""
Whether the integration is receiving alerts
"""
active: Boolean!
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
The name of the integration
"""
name: String!
"""
The project to create the integration in
"""
projectPath: ID!
}
"""
Autogenerated return type of HttpIntegrationCreate
"""
type HttpIntegrationCreatePayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Errors encountered during execution of the mutation.
"""
errors: [String!]!
"""
The updated HTTP integration
"""
integration: AlertManagementHttpIntegration
}
"""
Autogenerated input type of HttpIntegrationResetToken
"""
input HttpIntegrationResetTokenInput {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
The id of the integration to mutate
"""
id: AlertManagementHttpIntegrationID!
}
"""
Autogenerated return type of HttpIntegrationResetToken
"""
type HttpIntegrationResetTokenPayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Errors encountered during execution of the mutation.
"""
errors: [String!]!
"""
The updated HTTP integration
"""
integration: AlertManagementHttpIntegration
}
"""
Autogenerated input type of HttpIntegrationUpdate
"""
input HttpIntegrationUpdateInput {
"""
Whether the integration is receiving alerts
"""
active: Boolean
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
The id of the integration to mutate
"""
id: AlertManagementHttpIntegrationID!
"""
The name of the integration
"""
name: String
}
"""
Autogenerated return type of HttpIntegrationUpdate
"""
type HttpIntegrationUpdatePayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Errors encountered during execution of the mutation.
"""
errors: [String!]!
"""
The updated HTTP integration
"""
integration: AlertManagementHttpIntegration
}
"""
An ISO 8601-encoded date
"""
......@@ -12819,6 +12949,9 @@ type Mutation {
epicAddIssue(input: EpicAddIssueInput!): EpicAddIssuePayload
epicSetSubscription(input: EpicSetSubscriptionInput!): EpicSetSubscriptionPayload
epicTreeReorder(input: EpicTreeReorderInput!): EpicTreeReorderPayload
httpIntegrationCreate(input: HttpIntegrationCreateInput!): HttpIntegrationCreatePayload
httpIntegrationResetToken(input: HttpIntegrationResetTokenInput!): HttpIntegrationResetTokenPayload
httpIntegrationUpdate(input: HttpIntegrationUpdateInput!): HttpIntegrationUpdatePayload
issueMove(input: IssueMoveInput!): IssueMovePayload
issueMoveList(input: IssueMoveListInput!): IssueMoveListPayload
issueSetAssignees(input: IssueSetAssigneesInput!): IssueSetAssigneesPayload
......@@ -12849,6 +12982,9 @@ type Mutation {
pipelineCancel(input: PipelineCancelInput!): PipelineCancelPayload
pipelineDestroy(input: PipelineDestroyInput!): PipelineDestroyPayload
pipelineRetry(input: PipelineRetryInput!): PipelineRetryPayload
prometheusIntegrationCreate(input: PrometheusIntegrationCreateInput!): PrometheusIntegrationCreatePayload
prometheusIntegrationResetToken(input: PrometheusIntegrationResetTokenInput!): PrometheusIntegrationResetTokenPayload
prometheusIntegrationUpdate(input: PrometheusIntegrationUpdateInput!): PrometheusIntegrationUpdatePayload
removeAwardEmoji(input: RemoveAwardEmojiInput!): RemoveAwardEmojiPayload @deprecated(reason: "Use awardEmojiRemove. Deprecated in 13.2")
removeProjectFromSecurityDashboard(input: RemoveProjectFromSecurityDashboardInput!): RemoveProjectFromSecurityDashboardPayload
revertVulnerabilityToDetected(input: RevertVulnerabilityToDetectedInput!): RevertVulnerabilityToDetectedPayload @deprecated(reason: "Use vulnerabilityRevertToDetected. Deprecated in 13.5")
......@@ -16106,6 +16242,136 @@ type PrometheusAlert {
id: ID!
}
"""
Autogenerated input type of PrometheusIntegrationCreate
"""
input PrometheusIntegrationCreateInput {
"""
Whether the integration is receiving alerts
"""
active: Boolean!
"""
Endpoint at which prometheus can be queried
"""
apiUrl: String!
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
The project to create the integration in
"""
projectPath: ID!
}
"""
Autogenerated return type of PrometheusIntegrationCreate
"""
type PrometheusIntegrationCreatePayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Errors encountered during execution of the mutation.
"""
errors: [String!]!
"""
The newly created integration
"""
integration: AlertManagementPrometheusIntegration
}
"""
Autogenerated input type of PrometheusIntegrationResetToken
"""
input PrometheusIntegrationResetTokenInput {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
The id of the integration to mutate
"""
id: PrometheusServiceID!
}
"""
Autogenerated return type of PrometheusIntegrationResetToken
"""
type PrometheusIntegrationResetTokenPayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Errors encountered during execution of the mutation.
"""
errors: [String!]!
"""
The newly created integration
"""
integration: AlertManagementPrometheusIntegration
}
"""
Autogenerated input type of PrometheusIntegrationUpdate
"""
input PrometheusIntegrationUpdateInput {
"""
Whether the integration is receiving alerts
"""
active: Boolean
"""
Endpoint at which prometheus can be queried
"""
apiUrl: String
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
The id of the integration to mutate
"""
id: PrometheusServiceID!
}
"""
Autogenerated return type of PrometheusIntegrationUpdate
"""
type PrometheusIntegrationUpdatePayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Errors encountered during execution of the mutation.
"""
errors: [String!]!
"""
The newly created integration
"""
integration: AlertManagementPrometheusIntegration
}
"""
Identifier of PrometheusService
"""
scalar PrometheusServiceID
type Query {
"""
Get information about current user
......
......@@ -1327,6 +1327,36 @@ Represents a Group Membership.
| ----- | ---- | ----------- |
| `readGroup` | Boolean! | Indicates the user can perform `read_group` on this resource |
### HttpIntegrationCreatePayload
Autogenerated return type of HttpIntegrationCreate.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `integration` | AlertManagementHttpIntegration | The updated HTTP integration |
### HttpIntegrationResetTokenPayload
Autogenerated return type of HttpIntegrationResetToken.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `integration` | AlertManagementHttpIntegration | The updated HTTP integration |
### HttpIntegrationUpdatePayload
Autogenerated return type of HttpIntegrationUpdate.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `integration` | AlertManagementHttpIntegration | The updated HTTP integration |
### InstanceSecurityDashboard
| Field | Type | Description |
......@@ -2186,6 +2216,36 @@ The alert condition for Prometheus.
| `humanizedText` | String! | The human-readable text of the alert condition |
| `id` | ID! | ID of the alert condition |
### PrometheusIntegrationCreatePayload
Autogenerated return type of PrometheusIntegrationCreate.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `integration` | AlertManagementPrometheusIntegration | The newly created integration |
### PrometheusIntegrationResetTokenPayload
Autogenerated return type of PrometheusIntegrationResetToken.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `integration` | AlertManagementPrometheusIntegration | The newly created integration |
### PrometheusIntegrationUpdatePayload
Autogenerated return type of PrometheusIntegrationUpdate.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `integration` | AlertManagementPrometheusIntegration | The newly created integration |
### Release
Represents a release.
......
# frozen_string_literal: true
module EE
module AlertManagement
module HttpIntegrations
module CreateService
extend ::Gitlab::Utils::Override
private
override :creation_allowed?
def creation_allowed?
project.feature_available?(:multiple_alert_http_integrations) || super
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe AlertManagement::HttpIntegrations::CreateService do
let_it_be(:user) { create(:user) }
let_it_be_with_reload(:project) { create(:project) }
let(:params) { { name: 'New HTTP Integration' } }
let(:service) { described_class.new(project, user, params) }
before do
project.add_maintainer(user)
stub_licensed_features(multiple_alert_http_integrations: true)
end
describe '#execute' do
subject(:response) { service.execute }
context 'when an integration already exists' do
let_it_be(:existing_integration) { create(:alert_management_http_integration, project: project) }
it 'successfully creates a new integration' do
expect(response).to be_success
integration = response.payload[:integration]
expect(integration).to be_a(::AlertManagement::HttpIntegration)
expect(integration.name).to eq('New HTTP Integration')
expect(integration).not_to be_active
expect(integration.token).to be_present
expect(integration.endpoint_identifier).to be_present
end
end
end
end
......@@ -17468,9 +17468,15 @@ msgstr ""
msgid "Multi-project Runners cannot be removed"
msgstr ""
msgid "Multiple HTTP integrations are not supported for this project"
msgstr ""
msgid "Multiple IP address ranges are supported."
msgstr ""
msgid "Multiple Prometheus integrations are not supported"
msgstr ""
msgid "Multiple domains are supported."
msgstr ""
......@@ -30564,6 +30570,12 @@ msgstr ""
msgid "You have insufficient permissions to create a Todo for this alert"
msgstr ""
msgid "You have insufficient permissions to create an HTTP integration for this project"
msgstr ""
msgid "You have insufficient permissions to update this HTTP integration"
msgstr ""
msgid "You have no permissions"
msgstr ""
......
# 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
# 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
# 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
# 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
# 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
# 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
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Creating a new HTTP Integration' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:project) { create(:project) }
let(:variables) do
{
project_path: project.full_path,
active: false,
name: 'New HTTP Integration'
}
end
let(:mutation) do
graphql_mutation(:http_integration_create, variables) do
<<~QL
clientMutationId
errors
integration {
id
type
name
active
token
url
apiUrl
}
QL
end
end
let(:mutation_response) { graphql_mutation_response(:http_integration_create) }
before do
project.add_maintainer(current_user)
end
it 'creates a new integration' do
post_graphql_mutation(mutation, current_user: current_user)
new_integration = ::AlertManagement::HttpIntegration.last!
integration_response = mutation_response['integration']
expect(response).to have_gitlab_http_status(:success)
expect(integration_response['id']).to eq(GitlabSchema.id_from_object(new_integration).to_s)
expect(integration_response['type']).to eq('HTTP')
expect(integration_response['name']).to eq(new_integration.name)
expect(integration_response['active']).to eq(new_integration.active)
expect(integration_response['token']).to eq(new_integration.token)
expect(integration_response['url']).to eq(new_integration.url)
expect(integration_response['apiUrl']).to eq(nil)
end
[:project_path, :active, :name].each do |argument|
context "without required argument #{argument}" do
before do
variables.delete(argument)
end
it_behaves_like 'an invalid argument to the mutation', argument_name: argument
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Resetting a token on an existing HTTP Integration' do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:integration) { create(:alert_management_http_integration, project: project) }
let(:mutation) do
variables = {
id: GitlabSchema.id_from_object(integration).to_s
}
graphql_mutation(:http_integration_reset_token, variables) do
<<~QL
clientMutationId
errors
integration {
id
token
}
QL
end
end
let(:mutation_response) { graphql_mutation_response(:http_integration_reset_token) }
before do
project.add_maintainer(user)
end
it 'updates the integration' do
previous_token = integration.token
post_graphql_mutation(mutation, current_user: user)
integration_response = mutation_response['integration']
expect(response).to have_gitlab_http_status(:success)
expect(integration_response['id']).to eq(GitlabSchema.id_from_object(integration).to_s)
expect(integration_response['token']).not_to eq(previous_token)
expect(integration_response['token']).to eq(integration.reload.token)
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Updating an existing HTTP Integration' do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:integration) { create(:alert_management_http_integration, project: project) }
let(:mutation) do
variables = {
id: GitlabSchema.id_from_object(integration).to_s,
name: 'Modified Name',
active: false
}
graphql_mutation(:http_integration_update, variables) do
<<~QL
clientMutationId
errors
integration {
id
name
active
url
}
QL
end
end
let(:mutation_response) { graphql_mutation_response(:http_integration_update) }
before do
project.add_maintainer(user)
end
it 'updates the integration' do
post_graphql_mutation(mutation, current_user: user)
integration_response = mutation_response['integration']
expect(response).to have_gitlab_http_status(:success)
expect(integration_response['id']).to eq(GitlabSchema.id_from_object(integration).to_s)
expect(integration_response['name']).to eq('Modified Name')
expect(integration_response['active']).to be_falsey
expect(integration_response['url']).to include('modified-name')
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Creating a new Prometheus Integration' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:project) { create(:project) }
let(:variables) do
{
project_path: project.full_path,
active: false,
api_url: 'https://prometheus-url.com'
}
end
let(:mutation) do
graphql_mutation(:prometheus_integration_create, variables) do
<<~QL
clientMutationId
errors
integration {
id
type
name
active
token
url
apiUrl
}
QL
end
end
let(:mutation_response) { graphql_mutation_response(:prometheus_integration_create) }
before do
project.add_maintainer(current_user)
end
it 'creates a new integration' do
post_graphql_mutation(mutation, current_user: current_user)
new_integration = ::PrometheusService.last!
integration_response = mutation_response['integration']
expect(response).to have_gitlab_http_status(:success)
expect(integration_response['id']).to eq(GitlabSchema.id_from_object(new_integration).to_s)
expect(integration_response['type']).to eq('PROMETHEUS')
expect(integration_response['name']).to eq(new_integration.title)
expect(integration_response['active']).to eq(new_integration.manual_configuration?)
expect(integration_response['token']).to eq(new_integration.project.alerting_setting.token)
expect(integration_response['url']).to eq("http://localhost/#{project.full_path}/prometheus/alerts/notify.json")
expect(integration_response['apiUrl']).to eq(new_integration.api_url)
end
[:project_path, :active, :api_url].each do |argument|
context "without required argument #{argument}" do
before do
variables.delete(argument)
end
it_behaves_like 'an invalid argument to the mutation', argument_name: argument
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Resetting a token on an existing Prometheus Integration' do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:integration) { create(:prometheus_service, project: project) }
let(:mutation) do
variables = {
id: GitlabSchema.id_from_object(integration).to_s
}
graphql_mutation(:prometheus_integration_reset_token, variables) do
<<~QL
clientMutationId
errors
integration {
id
token
}
QL
end
end
let(:mutation_response) { graphql_mutation_response(:prometheus_integration_reset_token) }
before do
project.add_maintainer(user)
end
it 'creates a token' do
post_graphql_mutation(mutation, current_user: user)
integration_response = mutation_response['integration']
expect(response).to have_gitlab_http_status(:success)
expect(integration_response['id']).to eq(GitlabSchema.id_from_object(integration).to_s)
expect(integration_response['token']).not_to be_nil
expect(integration_response['token']).to eq(project.alerting_setting.token)
end
context 'with an existing alerting setting' do
let_it_be(:alerting_setting) { create(:project_alerting_setting, project: project) }
it 'updates the token' do
previous_token = alerting_setting.token
post_graphql_mutation(mutation, current_user: user)
integration_response = mutation_response['integration']
expect(response).to have_gitlab_http_status(:success)
expect(integration_response['id']).to eq(GitlabSchema.id_from_object(integration).to_s)
expect(integration_response['token']).not_to eq(previous_token)
expect(integration_response['token']).to eq(alerting_setting.reload.token)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Updating an existing Prometheus Integration' do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:integration) { create(:prometheus_service, project: project) }
let(:mutation) do
variables = {
id: GitlabSchema.id_from_object(integration).to_s,
api_url: 'http://modified-url.com',
active: true
}
graphql_mutation(:prometheus_integration_update, variables) do
<<~QL
clientMutationId
errors
integration {
id
active
apiUrl
}
QL
end
end
let(:mutation_response) { graphql_mutation_response(:prometheus_integration_update) }
before do
project.add_maintainer(user)
end
it 'updates the integration' do
post_graphql_mutation(mutation, current_user: user)
integration_response = mutation_response['integration']
expect(response).to have_gitlab_http_status(:success)
expect(integration_response['id']).to eq(GitlabSchema.id_from_object(integration).to_s)
expect(integration_response['apiUrl']).to eq('http://modified-url.com')
expect(integration_response['active']).to be_truthy
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe AlertManagement::HttpIntegrations::CreateService do
let_it_be(:user_with_permissions) { create(:user) }
let_it_be(:user_without_permissions) { create(:user) }
let_it_be_with_reload(:project) { create(:project) }
let(:current_user) { user_with_permissions }
let(:params) { {} }
let(:service) { described_class.new(project, current_user, params) }
before_all do
project.add_maintainer(user_with_permissions)
end
describe '#execute' do
shared_examples 'error response' do |message|
it 'has an informative message' do
expect(response).to be_error
expect(response.message).to eq(message)
end
end
subject(:response) { service.execute }
context 'when the current_user is anonymous' do
let(:current_user) { nil }
it_behaves_like 'error response', 'You have insufficient permissions to create an HTTP integration for this project'
end
context 'when current_user does not have permission to create integrations' do
let(:current_user) { user_without_permissions }
it_behaves_like 'error response', 'You have insufficient permissions to create an HTTP integration for this project'
end
context 'when feature flag is not enabled' do
before do
stub_feature_flags(multiple_http_integrations: false)
end
it_behaves_like 'error response', 'Multiple HTTP integrations are not supported for this project'
end
context 'when an integration already exists' do
let_it_be(:existing_integration) { create(:alert_management_http_integration, project: project) }
it_behaves_like 'error response', 'Multiple HTTP integrations are not supported for this project'
end
context 'when an error occurs during update' do
it_behaves_like 'error response', "Name can't be blank"
end
context 'with valid params' do
let(:params) { { name: 'New HTTP Integration', active: true } }
it 'successfully creates an integration' do
expect(response).to be_success
integration = response.payload[:integration]
expect(integration).to be_a(::AlertManagement::HttpIntegration)
expect(integration.name).to eq('New HTTP Integration')
expect(integration).to be_active
expect(integration.token).to be_present
expect(integration.endpoint_identifier).to be_present
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe AlertManagement::HttpIntegrations::UpdateService do
let_it_be(:user_with_permissions) { create(:user) }
let_it_be(:user_without_permissions) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be_with_reload(:integration) { create(:alert_management_http_integration, :inactive, project: project, name: 'Old Name') }
let(:current_user) { user_with_permissions }
let(:params) { {} }
let(:service) { described_class.new(integration, current_user, params) }
before_all do
project.add_maintainer(user_with_permissions)
end
describe '#execute' do
shared_examples 'error response' do |message|
it 'has an informative message' do
expect(response).to be_error
expect(response.message).to eq(message)
end
end
subject(:response) { service.execute }
context 'when the current_user is anonymous' do
let(:current_user) { nil }
it_behaves_like 'error response', 'You have insufficient permissions to update this HTTP integration'
end
context 'when current_user does not have permission to create integrations' do
let(:current_user) { user_without_permissions }
it_behaves_like 'error response', 'You have insufficient permissions to update this HTTP integration'
end
context 'when feature flag is not enabled' do
before do
stub_feature_flags(multiple_http_integrations: false)
end
it_behaves_like 'error response', 'Multiple HTTP integrations are not supported for this project'
end
context 'when an error occurs during update' do
let(:params) { { name: '' } }
it_behaves_like 'error response', "Name can't be blank"
end
context 'with name param' do
let(:params) { { name: 'New Name' } }
it 'successfully updates the integration' do
expect(response).to be_success
expect(response.payload[:integration].name).to eq('New Name')
end
end
context 'with active param' do
let(:params) { { active: true } }
it 'successfully updates the integration' do
expect(response).to be_success
expect(response.payload[:integration]).to be_active
end
end
context 'with regenerate_token flag' do
let(:params) { { regenerate_token: true } }
it 'successfully updates the integration' do
previous_token = integration.token
expect(response).to be_success
expect(response.payload[:integration].token).not_to eq(previous_token)
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment