Commit 3b495cc5 authored by Sanad Liaquat's avatar Sanad Liaquat

Add End-to-end test for basic Jira Integration

Update QA::Support::API's post and get to allow more params
Introduce support for accessing Jira Server via the API
parent aa735684
...@@ -16,3 +16,4 @@ This is a partial list of the [RSpec metadata](https://relishapp.com/rspec/rspec ...@@ -16,3 +16,4 @@ This is a partial list of the [RSpec metadata](https://relishapp.com/rspec/rspec
| `:runner` | The test depends on and will set up a GitLab Runner instance, typically to run a pipeline. | | `:runner` | The test depends on and will set up a GitLab Runner instance, typically to run a pipeline. |
| `:gitaly_ha` | The test will run against a GitLab instance where repositories are stored on redundant Gitaly nodes behind a Praefect node. All nodes are [separate containers](../../../administration/gitaly/praefect.md#requirements-for-configuring-a-gitaly-cluster). Tests that use this tag have a longer setup time since there are three additional containers that need to be started. | | `:gitaly_ha` | The test will run against a GitLab instance where repositories are stored on redundant Gitaly nodes behind a Praefect node. All nodes are [separate containers](../../../administration/gitaly/praefect.md#requirements-for-configuring-a-gitaly-cluster). Tests that use this tag have a longer setup time since there are three additional containers that need to be started. |
| `:skip_live_env` | The test will be excluded when run against live deployed environments such as Staging, Canary, and Production. | | `:skip_live_env` | The test will be excluded when run against live deployed environments such as Staging, Canary, and Production. |
| `:jira` | The test requires a Jira Server. [GitLab-QA](https://gitlab.com/gitlab-org/gitlab-qa) will provision the Jira Server in a docker container when the `Test::Integration::Jira` test scenario is run.
...@@ -254,6 +254,7 @@ module QA ...@@ -254,6 +254,7 @@ module QA
autoload :Main, 'qa/page/project/settings/main' autoload :Main, 'qa/page/project/settings/main'
autoload :Repository, 'qa/page/project/settings/repository' autoload :Repository, 'qa/page/project/settings/repository'
autoload :CICD, 'qa/page/project/settings/ci_cd' autoload :CICD, 'qa/page/project/settings/ci_cd'
autoload :Integrations, 'qa/page/project/settings/integrations'
autoload :GeneralPipelines, 'qa/page/project/settings/general_pipelines' autoload :GeneralPipelines, 'qa/page/project/settings/general_pipelines'
autoload :AutoDevops, 'qa/page/project/settings/auto_devops' autoload :AutoDevops, 'qa/page/project/settings/auto_devops'
autoload :DeployKeys, 'qa/page/project/settings/deploy_keys' autoload :DeployKeys, 'qa/page/project/settings/deploy_keys'
...@@ -265,6 +266,10 @@ module QA ...@@ -265,6 +266,10 @@ module QA
autoload :Members, 'qa/page/project/settings/members' autoload :Members, 'qa/page/project/settings/members'
autoload :MirroringRepositories, 'qa/page/project/settings/mirroring_repositories' autoload :MirroringRepositories, 'qa/page/project/settings/mirroring_repositories'
autoload :VisibilityFeaturesPermissions, 'qa/page/project/settings/visibility_features_permissions' autoload :VisibilityFeaturesPermissions, 'qa/page/project/settings/visibility_features_permissions'
module Services
autoload :Jira, 'qa/page/project/settings/services/jira'
end
autoload :Operations, 'qa/page/project/settings/operations' autoload :Operations, 'qa/page/project/settings/operations'
autoload :Incidents, 'qa/page/project/settings/incidents' autoload :Incidents, 'qa/page/project/settings/incidents'
autoload :Integrations, 'qa/page/project/settings/integrations' autoload :Integrations, 'qa/page/project/settings/integrations'
...@@ -512,6 +517,10 @@ module QA ...@@ -512,6 +517,10 @@ module QA
autoload :ConfigureJob, 'qa/vendor/jenkins/page/configure_job' autoload :ConfigureJob, 'qa/vendor/jenkins/page/configure_job'
end end
end end
module Jira
autoload :JiraAPI, 'qa/vendor/jira/jira_api'
end
end end
# Classes that provide support to other parts of the framework. # Classes that provide support to other parts of the framework.
......
...@@ -5,9 +5,15 @@ module QA ...@@ -5,9 +5,15 @@ module QA
module Page module Page
module Project module Project
module Settings module Settings
class Integrations < QA::Page::Base module Integrations
view 'app/views/shared/integrations/_index.html.haml' do extend QA::Page::PageConcern
element :jenkins_link, '{ data: { qa_selector: "#{integration.to_param' # rubocop:disable QA/ElementWithPattern
def self.prepended(base)
base.class_eval do
view 'app/views/shared/integrations/_index.html.haml' do
element :jenkins_link, '{ data: { qa_selector: "#{integration.to_param' # rubocop:disable QA/ElementWithPattern
end
end
end end
def click_jenkins_ci_link def click_jenkins_ci_link
......
...@@ -7,13 +7,20 @@ module QA ...@@ -7,13 +7,20 @@ module QA
class Integrations < QA::Page::Base class Integrations < QA::Page::Base
view 'app/views/shared/integrations/_index.html.haml' do view 'app/views/shared/integrations/_index.html.haml' do
element :prometheus_link, '{ data: { qa_selector: "#{integration.to_param' # rubocop:disable QA/ElementWithPattern element :prometheus_link, '{ data: { qa_selector: "#{integration.to_param' # rubocop:disable QA/ElementWithPattern
element :jira_link, '{ data: { qa_selector: "#{integration.to_param' # rubocop:disable QA/ElementWithPattern
end end
def click_on_prometheus_integration def click_on_prometheus_integration
click_element :prometheus_link click_element :prometheus_link
end end
def click_jira_link
click_element :jira_link
end
end end
end end
end end
end end
end end
QA::Page::Project::Settings::Integrations.prepend_if_ee('QA::EE::Page::Project::Settings::Integrations')
# frozen_string_literal: true
module QA
module Page
module Project
module Settings
module Services
class Jira < QA::Page::Base
view 'app/views/shared/_field.html.haml' do
element :url_field, 'data: { qa_selector: "#{name.downcase.gsub' # rubocop:disable QA/ElementWithPattern
element :username_field, 'data: { qa_selector: "#{name.downcase.gsub' # rubocop:disable QA/ElementWithPattern
element :password_field, 'data: { qa_selector: "#{name.downcase.gsub' # rubocop:disable QA/ElementWithPattern
element :jira_issue_transition_id_field, 'data: { qa_selector: "#{name.downcase.gsub' # rubocop:disable QA/ElementWithPattern
end
view 'app/helpers/services_helper.rb' do
element :save_changes_button
end
def setup_service_with(url:)
QA::Runtime::Logger.info "Setting up JIRA"
set_jira_server_url(url)
set_username(Runtime::Env.jira_admin_username)
set_password(Runtime::Env.jira_admin_password)
set_transaction_ids('11,21,31,41')
click_save_changes_button
wait_until(reload: false) do
has_element?(:save_changes_button, wait: 1) ? !find_element(:save_changes_button).disabled? : true
end
end
private
def set_jira_server_url(url)
fill_element(:url_field, url)
end
def set_username(username)
fill_element(:username_field, username)
end
def set_password(password)
fill_element(:password_field, password)
end
def set_transaction_ids(transaction_ids)
fill_element(:jira_issue_transition_id_field, transaction_ids)
end
def click_save_changes_button
click_element :save_changes_button
end
end
end
end
end
end
end
...@@ -109,6 +109,10 @@ module QA ...@@ -109,6 +109,10 @@ module QA
"#{api_get_path}/runners" "#{api_get_path}/runners"
end end
def api_repository_branches_path
"#{api_get_path}/repository/branches"
end
def api_pipelines_path def api_pipelines_path
"#{api_get_path}/pipelines" "#{api_get_path}/pipelines"
end end
...@@ -180,6 +184,11 @@ module QA ...@@ -180,6 +184,11 @@ module QA
parse_body(response) parse_body(response)
end end
def repository_branches
response = get Runtime::API::Request.new(api_client, api_repository_branches_path).url
parse_body(response)
end
def pipelines def pipelines
parse_body(get(Runtime::API::Request.new(api_client, api_pipelines_path).url)) parse_body(get(Runtime::API::Request.new(api_client, api_pipelines_path).url))
end end
......
...@@ -210,6 +210,18 @@ module QA ...@@ -210,6 +210,18 @@ module QA
ENV['GITLAB_QA_1P_GITHUB_UUID'] ENV['GITLAB_QA_1P_GITHUB_UUID']
end end
def jira_admin_username
ENV['JIRA_ADMIN_USERNAME']
end
def jira_admin_password
ENV['JIRA_ADMIN_PASSWORD']
end
def jira_hostname
ENV['JIRA_HOSTNAME']
end
def knapsack? def knapsack?
!!(ENV['KNAPSACK_GENERATE_REPORT'] || ENV['KNAPSACK_REPORT_PATH'] || ENV['KNAPSACK_TEST_FILE_PATTERN']) !!(ENV['KNAPSACK_GENERATE_REPORT'] || ENV['KNAPSACK_REPORT_PATH'] || ENV['KNAPSACK_TEST_FILE_PATTERN'])
end end
......
# frozen_string_literal: true
module QA
context 'Create' do
include Support::Api
describe 'Jira integration', :jira, :orchestrated, :requires_admin do
let(:jira_project_key) { 'JITP' }
before(:all) do
page.visit Vendor::Jira::JiraAPI.perform(&:base_url)
QA::Support::Retrier.retry_until(sleep_interval: 3, reload_page: page, max_attempts: 20, raise_on_failure: true) do
page.has_text? 'Welcome to Jira'
end
@project = Resource::Project.fabricate_via_api! do |project|
project.name = "project_with_jira_integration"
end
# Retry is required because allow_local_requests_from_web_hooks_and_services
# takes some time to get enabled.
# Bug issue: https://gitlab.com/gitlab-org/gitlab/-/issues/217010
QA::Support::Retrier.retry_on_exception(max_attempts: 5, sleep_interval: 3) do
Runtime::ApplicationSettings.set_application_settings(allow_local_requests_from_web_hooks_and_services: true)
page.visit Runtime::Scenario.gitlab_address
Flow::Login.sign_in_unless_signed_in
@project.visit!
Page::Project::Menu.perform(&:go_to_integrations_settings)
QA::Page::Project::Settings::Integrations.perform(&:click_jira_link)
QA::Page::Project::Settings::Services::Jira.perform do |jira|
jira.setup_service_with(url: Vendor::Jira::JiraAPI.perform(&:base_url))
end
expect(page).not_to have_text("Requests to the local network are not allowed")
end
end
it 'closes an issue via pushing a commit' do
issue_key = Vendor::Jira::JiraAPI.perform do |jira_api|
jira_api.create_issue(jira_project_key)
end
push_commit("Closes #{issue_key}")
expect_issue_done(issue_key)
end
it 'closes an issue via a merge request' do
issue_key = Vendor::Jira::JiraAPI.perform do |jira_api|
jira_api.create_issue(jira_project_key)
end
page.visit Runtime::Scenario.gitlab_address
Flow::Login.sign_in_unless_signed_in
merge_request = create_mr_with_description("Closes #{issue_key}")
merge_request.visit!
Page::MergeRequest::Show.perform(&:merge!)
expect_issue_done(issue_key)
end
def create_mr_with_description(description)
Resource::MergeRequest.fabricate! do |merge_request|
merge_request.project = @project
merge_request.target_new_branch = !master_branch_exists?
merge_request.description = description
end
end
def push_commit(commit_message)
Resource::Repository::ProjectPush.fabricate! do |push|
push.branch_name = 'master'
push.commit_message = commit_message
push.file_content = commit_message
push.project = @project
push.new_branch = !master_branch_exists?
end
end
def expect_issue_done(issue_key)
expect do
Support::Waiter.wait_until(raise_on_failure: true) do
jira_issue = Vendor::Jira::JiraAPI.perform do |jira_api|
jira_api.fetch_issue(issue_key)
end
jira_issue[:fields][:status][:name] == 'Done'
end
end.not_to raise_error
end
def master_branch_exists?
@project.repository_branches.map { |item| item[:name] }.include?("master")
end
end
end
end
...@@ -131,7 +131,7 @@ module QA ...@@ -131,7 +131,7 @@ module QA
Page::Project::Menu.perform(&:click_project) Page::Project::Menu.perform(&:click_project)
Page::Project::Menu.perform(&:go_to_integrations_settings) Page::Project::Menu.perform(&:go_to_integrations_settings)
QA::EE::Page::Project::Settings::Integrations.perform(&:click_jenkins_ci_link) Page::Project::Settings::Integrations.perform(&:click_jenkins_ci_link)
QA::EE::Page::Project::Settings::Services::Jenkins.perform do |jenkins| QA::EE::Page::Project::Settings::Services::Jenkins.perform do |jenkins|
jenkins.setup_service_with(jenkins_url: patch_host_name(Vendor::Jenkins::Page::Base.host, 'jenkins-server'), jenkins.setup_service_with(jenkins_url: patch_host_name(Vendor::Jenkins::Page::Base.host, 'jenkins-server'),
......
...@@ -11,22 +11,31 @@ module QA ...@@ -11,22 +11,31 @@ module QA
HTTP_STATUS_ACCEPTED = 202 HTTP_STATUS_ACCEPTED = 202
HTTP_STATUS_SERVER_ERROR = 500 HTTP_STATUS_SERVER_ERROR = 500
def post(url, payload) def post(url, payload, args = {})
RestClient::Request.execute( default_args = {
method: :post, method: :post,
url: url, url: url,
payload: payload, payload: payload,
verify_ssl: false) verify_ssl: false
}
RestClient::Request.execute(
default_args.merge(args)
)
rescue RestClient::ExceptionWithResponse => e rescue RestClient::ExceptionWithResponse => e
return_response_or_raise(e) return_response_or_raise(e)
end end
def get(url, raw_response: false) def get(url, args = {})
RestClient::Request.execute( default_args = {
method: :get, method: :get,
url: url, url: url,
verify_ssl: false, verify_ssl: false
raw_response: raw_response) }
RestClient::Request.execute(
default_args.merge(args)
)
rescue RestClient::ExceptionWithResponse => e rescue RestClient::ExceptionWithResponse => e
return_response_or_raise(e) return_response_or_raise(e)
end end
......
# frozen_string_literal: true
module QA
module Vendor
module Jira
class JiraAPI
include Scenario::Actable
include Support::Api
def base_url
host = QA::Runtime::Env.jira_hostname || 'localhost'
"http://#{host}:8080"
end
def api_url
"#{base_url}/rest/api/2"
end
def fetch_issue(issue_key)
response = get("#{api_url}/issue/#{issue_key}", user: Runtime::Env.jira_admin_username, password: Runtime::Env.jira_admin_password)
parse_body(response)
end
def create_issue(jira_project_key)
payload = {
fields: {
project: {
key: jira_project_key
},
summary: 'REST ye merry gentlemen.',
description: 'Creating of an issue using project keys and issue type names using the REST API',
issuetype: {
name: 'Bug'
}
}
}
response = post("#{api_url}/issue",
payload.to_json, headers: { 'Content-Type': 'application/json' },
user: Runtime::Env.jira_admin_username,
password: Runtime::Env.jira_admin_password)
issue_key = parse_body(response)[:key]
QA::Runtime::Logger.debug("Created JIRA issue with key: '#{issue_key}'")
issue_key
end
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