560 lines
22 KiB
Ruby
560 lines
22 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "spec_helper"
|
|
|
|
RSpec.describe API::Integrations, feature_category: :integrations do
|
|
include Integrations::TestHelpers
|
|
|
|
let_it_be(:user) { create(:user) }
|
|
let_it_be(:user2) { create(:user) }
|
|
let_it_be(:project, reload: true) { create(:project, creator_id: user.id, namespace: user.namespace, owners: [user]) }
|
|
let_it_be(:project2) { create(:project, creator_id: user.id, namespace: user.namespace) }
|
|
let_it_be(:group, reload: true) { create(:group, owners: user) }
|
|
|
|
context "Project level integrations" do
|
|
let_it_be(:available_integration_names) do
|
|
excluded_integrations = [Integrations::GitlabSlackApplication.to_param, Integrations::Zentao.to_param]
|
|
Integration.available_integration_names(include_instance_specific: false) - excluded_integrations
|
|
end
|
|
|
|
let_it_be(:project_integrations_map) do
|
|
available_integration_names.index_with do |name|
|
|
create(integration_factory(name), :inactive, project: project)
|
|
end
|
|
end
|
|
|
|
%w[integrations services].each do |endpoint|
|
|
describe "GET /projects/:id/#{endpoint}" do
|
|
it 'returns authentication error when unauthenticated' do
|
|
get api("/projects/#{project.id}/#{endpoint}")
|
|
|
|
expect(response).to have_gitlab_http_status(:unauthorized)
|
|
end
|
|
|
|
it "returns error when authenticated but user is not a project owner" do
|
|
project.add_developer(user2)
|
|
get api("/projects/#{project.id}/#{endpoint}", user2)
|
|
|
|
expect(response).to have_gitlab_http_status(:forbidden)
|
|
end
|
|
|
|
it "returns a list of all active integrations" do
|
|
get api("/projects/#{project.id}/#{endpoint}", user)
|
|
|
|
aggregate_failures 'expect successful response with all active integrations' do
|
|
expect(response).to have_gitlab_http_status(:ok)
|
|
expect(json_response).to be_an Array
|
|
expect(json_response.count).to eq(1)
|
|
expect(json_response.first['slug']).to eq('prometheus')
|
|
expect(response).to match_response_schema('public_api/v4/integrations')
|
|
end
|
|
end
|
|
end
|
|
|
|
where(:integration) do
|
|
# The integrations API supports all project integrations.
|
|
# You cannot create a GitLab for Slack app. You must install the app from the GitLab UI.
|
|
unavailable_integration_names = [
|
|
Integrations::GitlabSlackApplication.to_param,
|
|
Integrations::JiraCloudApp.to_param,
|
|
Integrations::Prometheus.to_param,
|
|
Integrations::Zentao.to_param
|
|
]
|
|
|
|
names = Integration.available_integration_names(include_instance_specific: false)
|
|
names.reject { |name| unavailable_integration_names.include?(name) }
|
|
end
|
|
|
|
with_them do
|
|
integration = params[:integration]
|
|
|
|
describe "PUT /projects/:id/#{endpoint}/#{integration.dasherize}" do
|
|
it_behaves_like 'set up an integration', endpoint: endpoint, integration: integration, parent_resource_name: 'project' do
|
|
let(:parent_resource) { project }
|
|
let(:integrations_map) { project_integrations_map }
|
|
end
|
|
end
|
|
|
|
describe "DELETE /projects/:id/#{endpoint}/#{integration.dasherize}" do
|
|
it_behaves_like 'disable an integration', endpoint: endpoint, integration: integration, parent_resource_name: 'project' do
|
|
let(:parent_resource) { project }
|
|
let(:integrations_map) { project_integrations_map }
|
|
end
|
|
end
|
|
|
|
describe "GET /projects/:id/#{endpoint}/#{integration.dasherize}" do
|
|
it_behaves_like 'get an integration settings', endpoint: endpoint, integration: integration, parent_resource_name: 'project' do
|
|
let(:parent_resource) { project }
|
|
let(:integrations_map) { project_integrations_map }
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "POST /projects/:id/#{endpoint}/:slug/trigger" do
|
|
describe 'Mattermost integration' do
|
|
let(:integration_name) { 'mattermost_slash_commands' }
|
|
|
|
context 'when no integration is available' do
|
|
it 'returns a not found message' do
|
|
post api("/projects/#{project.id}/#{endpoint}/idonotexist/trigger")
|
|
|
|
expect(response).to have_gitlab_http_status(:not_found)
|
|
expect(json_response["error"]).to eq("404 Not Found")
|
|
end
|
|
end
|
|
|
|
context 'when the integration exists' do
|
|
let(:params) { { token: 'secrettoken' } }
|
|
|
|
context 'when the integration is not active' do
|
|
before do
|
|
project_integrations_map[integration_name].deactivate!
|
|
end
|
|
|
|
it 'when the integration is inactive' do
|
|
post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: params
|
|
|
|
expect(response).to have_gitlab_http_status(:not_found)
|
|
end
|
|
end
|
|
|
|
context 'when the integration is active' do
|
|
before do
|
|
project_integrations_map[integration_name].activate!
|
|
end
|
|
|
|
it 'returns status 200' do
|
|
post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: params
|
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
|
end
|
|
end
|
|
|
|
context 'when the project can not be found' do
|
|
it 'returns a generic 404' do
|
|
post api("/projects/404/#{endpoint}/#{integration_name}/trigger"), params: params
|
|
|
|
expect(response).to have_gitlab_http_status(:not_found)
|
|
expect(json_response["message"]).to eq("404 Integration Not Found")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'Slack Integration' do
|
|
let(:integration_name) { 'slack_slash_commands' }
|
|
let(:params) { { token: 'secrettoken', text: 'help' } }
|
|
|
|
context 'when no integration is available' do
|
|
it 'returns a not found message' do
|
|
post api("/projects/#{project.id}/#{endpoint}/idonotexist/trigger")
|
|
|
|
expect(response).to have_gitlab_http_status(:not_found)
|
|
expect(json_response["error"]).to eq("404 Not Found")
|
|
end
|
|
end
|
|
|
|
context 'when the integration exists' do
|
|
context 'when the integration is not active' do
|
|
before do
|
|
project_integrations_map[integration_name].deactivate!
|
|
end
|
|
|
|
it 'when the integration is inactive' do
|
|
post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: params
|
|
|
|
expect(response).to have_gitlab_http_status(:not_found)
|
|
end
|
|
end
|
|
|
|
context 'when the integration is active' do
|
|
before do
|
|
project_integrations_map[integration_name].activate!
|
|
end
|
|
|
|
it 'returns status 200' do
|
|
post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: params
|
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
|
expect(json_response['response_type']).to eq("ephemeral")
|
|
end
|
|
end
|
|
|
|
context 'when the project can not be found' do
|
|
it 'returns a generic 404' do
|
|
post api("/projects/404/#{endpoint}/#{integration_name}/trigger"), params: params
|
|
|
|
expect(response).to have_gitlab_http_status(:not_found)
|
|
expect(json_response["message"]).to eq("404 Integration Not Found")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'Mattermost integration' do
|
|
let(:integration_name) { 'mattermost' }
|
|
let(:params) do
|
|
{ webhook: 'https://hook.example.com', username: 'username' }
|
|
end
|
|
|
|
before do
|
|
project_integrations_map[integration_name].activate!
|
|
end
|
|
|
|
it 'accepts a username for update' do
|
|
put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: params.merge(username: 'new_username')
|
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
|
expect(json_response['properties']['username']).to eq('new_username')
|
|
end
|
|
end
|
|
|
|
describe 'Microsoft Teams integration' do
|
|
let_it_be(:group) { create(:group) }
|
|
let(:integration_name) { 'microsoft-teams' }
|
|
let(:params) do
|
|
{
|
|
webhook: 'https://hook.example.com',
|
|
branches_to_be_notified: 'default',
|
|
notify_only_broken_pipelines: false
|
|
}
|
|
end
|
|
|
|
before do
|
|
create(:microsoft_teams_integration, group: group, project: nil)
|
|
project.update!(namespace: group)
|
|
project_integrations_map[integration_name.underscore].activate!
|
|
end
|
|
|
|
it 'accepts branches_to_be_notified for update' do
|
|
put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user),
|
|
params: params.merge(branches_to_be_notified: 'all')
|
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
|
expect(json_response['properties']['branches_to_be_notified']).to eq('all')
|
|
end
|
|
|
|
it 'accepts notify_only_broken_pipelines for update' do
|
|
put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user),
|
|
params: params.merge(notify_only_broken_pipelines: true)
|
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
|
expect(json_response['properties']['notify_only_broken_pipelines']).to eq(true)
|
|
end
|
|
|
|
it 'accepts `use_inherited_settings` for inheritance' do
|
|
expect do
|
|
put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user),
|
|
params: params.merge(use_inherited_settings: true)
|
|
end.to change { project_integrations_map[integration_name.underscore].reload.inherit_from_id }.from(nil)
|
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
|
expect(json_response['inherited']).to eq(true)
|
|
end
|
|
end
|
|
|
|
describe 'Hangouts Chat integration' do
|
|
let(:integration_name) { 'hangouts-chat' }
|
|
let(:params) do
|
|
{
|
|
webhook: 'https://hook.example.com',
|
|
branches_to_be_notified: 'default'
|
|
}
|
|
end
|
|
|
|
before do
|
|
project_integrations_map[integration_name.underscore].activate!
|
|
end
|
|
|
|
it 'accepts branches_to_be_notified for update', :aggregate_failures do
|
|
put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: params.merge(branches_to_be_notified: 'all')
|
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
|
expect(json_response['properties']['branches_to_be_notified']).to eq('all')
|
|
end
|
|
|
|
it 'only requires the webhook param' do
|
|
put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: { webhook: 'https://hook.example.com' }
|
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
|
end
|
|
end
|
|
|
|
describe 'Jira integration' do
|
|
let(:integration_name) { 'jira' }
|
|
let(:params) do
|
|
{ url: 'https://jira.example.com', username: 'username', password: 'password', jira_auth_type: 0 }
|
|
end
|
|
|
|
before do
|
|
project_integrations_map[integration_name].properties = params
|
|
project_integrations_map[integration_name].activate!
|
|
end
|
|
|
|
it 'returns the jira_issue_transition_id for get request' do
|
|
get api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user)
|
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
|
expect(json_response['properties']).to include('jira_issue_transition_id' => '56-1')
|
|
end
|
|
|
|
it 'returns the jira_issue_transition_id for put request' do
|
|
put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: params.merge(jira_issue_transition_id: '1')
|
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
|
expect(json_response['properties']['jira_issue_transition_id']).to eq('1')
|
|
end
|
|
end
|
|
|
|
describe 'Pipelines Email Integration' do
|
|
let(:integration_name) { 'pipelines-email' }
|
|
|
|
context 'notify_only_broken_pipelines property was saved as a string' do
|
|
before do
|
|
project_integrations_map[integration_name.underscore].activate!
|
|
end
|
|
|
|
it 'returns boolean values for notify_only_broken_pipelines' do
|
|
get api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user)
|
|
|
|
expect(json_response['properties']['notify_only_broken_pipelines']).to eq(true)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'GitLab for Slack app integration' do
|
|
before do
|
|
stub_application_setting(slack_app_enabled: true)
|
|
create(:gitlab_slack_application_integration, project: project)
|
|
end
|
|
|
|
describe "PUT /projects/:id/#{endpoint}/gitlab-slack-application" do
|
|
context 'for integration creation' do
|
|
before do
|
|
project.gitlab_slack_application_integration.destroy!
|
|
end
|
|
|
|
it 'returns 422' do
|
|
put api("/projects/#{project.id}/#{endpoint}/gitlab-slack-application", user)
|
|
|
|
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
|
expect(json_response['message']).to eq('You cannot create the GitLab for Slack app integration from the API')
|
|
end
|
|
end
|
|
|
|
context 'for integration update' do
|
|
before do
|
|
project.gitlab_slack_application_integration.update!(active: false)
|
|
end
|
|
|
|
it "does not enable the integration" do
|
|
put api("/projects/#{project.id}/#{endpoint}/gitlab-slack-application", user)
|
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
|
expect(project.gitlab_slack_application_integration.reload).to have_attributes(active: false)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "GET /projects/:id/#{endpoint}/gitlab-slack-application" do
|
|
it "fetches the integration and returns the correct fields" do
|
|
get api("/projects/#{project.id}/#{endpoint}/gitlab-slack-application", user)
|
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
|
assert_correct_response_fields(json_response['properties'].keys, project.gitlab_slack_application_integration)
|
|
end
|
|
end
|
|
|
|
describe "DELETE /projects/:id/#{endpoint}/gitlab-slack-application" do
|
|
it "disables the integration" do
|
|
expect { delete api("/projects/#{project.id}/#{endpoint}/gitlab-slack-application", user) }
|
|
.to change { project.gitlab_slack_application_integration.reload.activated? }.from(true).to(false)
|
|
|
|
expect(response).to have_gitlab_http_status(:no_content)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'GitLab for Jira Cloud app integration' do
|
|
before do
|
|
stub_application_setting(jira_connect_application_key: 'mock_key')
|
|
create(:jira_cloud_app_integration, project: project)
|
|
end
|
|
|
|
describe "PUT /projects/:id/#{endpoint}/jira-cloud-app" do
|
|
context 'for integration creation' do
|
|
before do
|
|
project.jira_cloud_app_integration.destroy!
|
|
end
|
|
|
|
it 'returns 422' do
|
|
put api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user)
|
|
|
|
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
|
expect(json_response['message']).to eq('You cannot create the GitLab for Jira Cloud app integration from the API')
|
|
end
|
|
end
|
|
|
|
context 'for integration update' do
|
|
before do
|
|
project.jira_cloud_app_integration.update!(active: false)
|
|
end
|
|
|
|
it "does not enable the integration" do
|
|
put api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user)
|
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
|
expect(project.jira_cloud_app_integration.reload).to have_attributes(active: false)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "GET /projects/:id/#{endpoint}/jira-cloud-app" do
|
|
it "fetches the integration and returns the correct fields" do
|
|
get api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user)
|
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
|
assert_correct_response_fields(json_response['properties'].keys, project.jira_cloud_app_integration)
|
|
end
|
|
end
|
|
|
|
describe "DELETE /projects/:id/#{endpoint}/jira-cloud-app" do
|
|
it "does not disable the integration" do
|
|
expect { delete api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user) }
|
|
.not_to change { project.jira_cloud_app_integration.reload.activated? }.from(true)
|
|
|
|
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
|
expect(json_response['message']).to eq('You cannot disable the GitLab for Jira Cloud app integration from the API')
|
|
end
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def assert_correct_response_fields(response_keys, integration)
|
|
assert_fields_match_integration(response_keys, integration)
|
|
assert_secret_fields_filtered(response_keys, integration)
|
|
end
|
|
|
|
def assert_fields_match_integration(response_keys, integration)
|
|
expect(response_keys).to match_array(integration.api_field_names)
|
|
end
|
|
|
|
def assert_secret_fields_filtered(response_keys, integration)
|
|
expect(response_keys).not_to include(*integration.secret_fields) unless integration.secret_fields.empty?
|
|
end
|
|
end
|
|
end
|
|
|
|
context "Group level integrations" do
|
|
let_it_be(:available_integration_names) do
|
|
excluded_integrations = [Integrations::GitlabSlackApplication.to_param, Integrations::Zentao.to_param]
|
|
Integration.available_integration_names(include_project_specific: false, include_instance_specific: false) - excluded_integrations
|
|
end
|
|
|
|
let_it_be(:group_integrations_map) do
|
|
available_integration_names.index_with do |name|
|
|
create(integration_factory(name), :inactive, :group, group: group)
|
|
end
|
|
end
|
|
|
|
'integrations'.tap do |endpoint|
|
|
describe "GET /groups/:id/#{endpoint}" do
|
|
let_it_be(:project_only_integration) { Integration::PROJECT_LEVEL_ONLY_INTEGRATION_NAMES }
|
|
|
|
it 'returns authentication error when unauthenticated' do
|
|
get api("/groups/#{group.id}/#{endpoint}")
|
|
|
|
expect(response).to have_gitlab_http_status(:unauthorized)
|
|
end
|
|
|
|
it "returns error when authenticated but user is not a group owner" do
|
|
group.add_developer(user2)
|
|
get api("/groups/#{group.id}/#{endpoint}", user2)
|
|
|
|
expect(response).to have_gitlab_http_status(:forbidden)
|
|
end
|
|
|
|
it "returns a list of all active integrations" do
|
|
get api("/groups/#{group.id}/#{endpoint}", user)
|
|
|
|
aggregate_failures 'expect successful response with all active integrations' do
|
|
expect(response).to have_gitlab_http_status(:ok)
|
|
expect(json_response).to be_an Array
|
|
expect(json_response.count).to eq(1)
|
|
expect(json_response.first['slug']).to eq('prometheus')
|
|
expect(response).to match_response_schema('public_api/v4/integrations')
|
|
end
|
|
end
|
|
|
|
Integration::PROJECT_LEVEL_ONLY_INTEGRATION_NAMES.map(&:dasherize).each do |integration_name|
|
|
it "returns 400 when trying to access a project-only integration" do
|
|
get api("/groups/#{group.id}/integrations/#{integration_name}", user)
|
|
|
|
expect(response).to have_gitlab_http_status(:bad_request)
|
|
expect(json_response["message"]).to eq("400 Integration not available")
|
|
end
|
|
end
|
|
end
|
|
|
|
where(:integration) do
|
|
# You cannot create a GitLab for Slack app. You must install the app from the GitLab UI.
|
|
unavailable_integration_names = [
|
|
Integrations::GitlabSlackApplication.to_param,
|
|
Integrations::Zentao.to_param,
|
|
Integrations::Prometheus.to_param
|
|
]
|
|
|
|
names = Integration.available_integration_names(include_instance_specific: false, include_project_specific: false)
|
|
names.reject { |name| unavailable_integration_names.include?(name) }
|
|
end
|
|
|
|
with_them do
|
|
integration = params[:integration]
|
|
|
|
describe "PUT /groups/:id/#{endpoint}/#{integration.dasherize}" do
|
|
it_behaves_like 'set up an integration', endpoint: endpoint, integration: integration, parent_resource_name: 'group' do
|
|
let(:parent_resource) { group }
|
|
let(:integrations_map) { group_integrations_map }
|
|
end
|
|
end
|
|
|
|
describe "DELETE /groups/:id/#{endpoint}/#{integration.dasherize}" do
|
|
it_behaves_like 'disable an integration', endpoint: endpoint, integration: integration, parent_resource_name: 'group' do
|
|
let(:parent_resource) { group }
|
|
let(:integrations_map) { group_integrations_map }
|
|
end
|
|
end
|
|
|
|
describe "GET /groups/:id/#{endpoint}/#{integration.dasherize}" do
|
|
it_behaves_like 'get an integration settings', endpoint: endpoint, integration: integration, parent_resource_name: 'group' do
|
|
let(:parent_resource) { group }
|
|
let(:integrations_map) { group_integrations_map }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'POST /slack/trigger' do
|
|
before do
|
|
stub_application_setting(slack_app_verification_token: 'token')
|
|
end
|
|
|
|
it 'returns status 200' do
|
|
post api('/slack/trigger'), params: { token: 'token', text: 'help' }
|
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
|
expect(json_response['response_type']).to eq("ephemeral")
|
|
end
|
|
|
|
it 'returns status 404 when token is invalid' do
|
|
post api('/slack/trigger'), params: { token: 'invalid', text: 'foo' }
|
|
|
|
expect(response).to have_gitlab_http_status(:not_found)
|
|
expect(json_response['response_type']).to be_blank
|
|
end
|
|
end
|
|
end
|