Commit 241e2e87 authored by Martin Cabrera's avatar Martin Cabrera

Merge branch 'master' into i-#25814-500-error

parents c2283a2d 28f633a9
...@@ -101,7 +101,7 @@ gem 'seed-fu', '~> 2.3.5' ...@@ -101,7 +101,7 @@ gem 'seed-fu', '~> 2.3.5'
# Markdown and HTML processing # Markdown and HTML processing
gem 'html-pipeline', '~> 1.11.0' gem 'html-pipeline', '~> 1.11.0'
gem 'deckar01-task_list', '1.0.6', require: 'task_list/railtie' gem 'deckar01-task_list', '1.0.6', require: 'task_list/railtie'
gem 'gitlab-markup', '~> 1.5.0' gem 'gitlab-markup', '~> 1.5.1'
gem 'redcarpet', '~> 3.3.3' gem 'redcarpet', '~> 3.3.3'
gem 'RedCloth', '~> 4.3.2' gem 'RedCloth', '~> 4.3.2'
gem 'rdoc', '~> 4.2' gem 'rdoc', '~> 4.2'
......
...@@ -73,12 +73,12 @@ ...@@ -73,12 +73,12 @@
<table class="table ci-table"> <table class="table ci-table">
<thead> <thead>
<tr> <tr>
<th>Status</th> <th class="pipeline-status">Status</th>
<th>Pipeline</th> <th class="pipeline-info">Pipeline</th>
<th>Commit</th> <th class="pipeline-commit">Commit</th>
<th>Stages</th> <th class="pipeline-stages">Stages</th>
<th></th> <th class="pipeline-date"></th>
<th class="hidden-xs"></th> <th class="pipeline-actions hidden-xs"></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
......
...@@ -109,6 +109,10 @@ ...@@ -109,6 +109,10 @@
.avatar { .avatar {
float: none; float: none;
} }
> a:not(:last-of-type) {
margin-right: 5px;
}
} }
} }
......
module ServiceParams module ServiceParams
extend ActiveSupport::Concern extend ActiveSupport::Concern
ALLOWED_PARAMS = [:title, :token, :type, :active, :api_key, :api_url, :api_version, :subdomain, ALLOWED_PARAMS_CE = [
:room, :recipients, :project_url, :webhook, :active,
:user_key, :device, :priority, :sound, :bamboo_url, :username, :password, :add_pusher,
:build_key, :server, :teamcity_url, :drone_url, :build_type, :api_key,
:description, :issues_url, :new_issue_url, :restrict_to_branch, :channel, :api_url,
:colorize_messages, :channels, :api_version,
:bamboo_url,
:build_key,
:build_type,
:ca_pem,
:channel,
:channels,
:color,
:colorize_messages,
:confidential_issues_events,
:default_irc_uri,
:description,
:device,
:disable_diffs,
:drone_url,
:enable_ssl_verification,
:external_wiki_url,
# We're using `issues_events` and `merge_requests_events` # We're using `issues_events` and `merge_requests_events`
# in the view so we still need to explicitly state them # in the view so we still need to explicitly state them
# here. `Service#event_names` would only give # here. `Service#event_names` would only give
# `issue_events` and `merge_request_events` (singular!) # `issue_events` and `merge_request_events` (singular!)
# See app/helpers/services_helper.rb for how we # See app/helpers/services_helper.rb for how we
# make those event names plural as special case. # make those event names plural as special case.
:issues_events, :confidential_issues_events, :merge_requests_events, :issues_events,
:notify_only_broken_builds, :notify_only_broken_pipelines, :issues_url,
:add_pusher, :send_from_committer_email, :disable_diffs, :jira_issue_transition_id,
:external_wiki_url, :notify, :color, :merge_requests_events,
:server_host, :server_port, :default_irc_uri, :enable_ssl_verification, :namespace,
:jira_issue_transition_id, :url, :project_key, :ca_pem, :namespace] :new_issue_url,
:notify,
:notify_only_broken_builds,
:notify_only_broken_pipelines,
:password,
:priority,
:project_key,
:project_url,
:recipients,
:restrict_to_branch,
:room,
:send_from_committer_email,
:server,
:server_host,
:server_port,
:sound,
:subdomain,
:teamcity_url,
:title,
:token,
:type,
:url,
:user_key,
:username,
:webhook
]
# Parameters to ignore if no value is specified # Parameters to ignore if no value is specified
FILTER_BLANK_PARAMS = [:password] FILTER_BLANK_PARAMS = [:password]
def service_params def service_params
dynamic_params = @service.event_channel_names + @service.event_names dynamic_params = @service.event_channel_names + @service.event_names
service_params = params.permit(:id, service: ALLOWED_PARAMS + dynamic_params) service_params = params.permit(:id, service: ALLOWED_PARAMS_CE + dynamic_params)
if service_params[:service].is_a?(Hash) if service_params[:service].is_a?(Hash)
FILTER_BLANK_PARAMS.each do |param| FILTER_BLANK_PARAMS.each do |param|
......
...@@ -125,7 +125,11 @@ class GroupsController < Groups::ApplicationController ...@@ -125,7 +125,11 @@ class GroupsController < Groups::ApplicationController
end end
def group_params def group_params
params.require(:group).permit( params.require(:group).permit(group_params_ce)
end
def group_params_ce
[
:avatar, :avatar,
:description, :description,
:lfs_enabled, :lfs_enabled,
...@@ -135,7 +139,7 @@ class GroupsController < Groups::ApplicationController ...@@ -135,7 +139,7 @@ class GroupsController < Groups::ApplicationController
:request_access_enabled, :request_access_enabled,
:share_with_group_lock, :share_with_group_lock,
:visibility_level :visibility_level
) ]
end end
def load_events def load_events
......
...@@ -409,10 +409,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -409,10 +409,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
else else
ci_service = @merge_request.source_project.try(:ci_service) ci_service = @merge_request.source_project.try(:ci_service)
status = ci_service.commit_status(merge_request.diff_head_sha, merge_request.source_branch) if ci_service status = ci_service.commit_status(merge_request.diff_head_sha, merge_request.source_branch) if ci_service
if ci_service.respond_to?(:commit_coverage)
coverage = ci_service.commit_coverage(merge_request.diff_head_sha, merge_request.source_branch)
end
end end
response = { response = {
......
...@@ -137,4 +137,10 @@ class CommitStatus < ActiveRecord::Base ...@@ -137,4 +137,10 @@ class CommitStatus < ActiveRecord::Base
.new(self, current_user) .new(self, current_user)
.fabricate! .fabricate!
end end
def sortable_name
name.split(/(\d+)/).map do |v|
v =~ /\d+/ ? v.to_i : v
end
end
end end
...@@ -32,6 +32,6 @@ module ProjectFeaturesCompatibility ...@@ -32,6 +32,6 @@ module ProjectFeaturesCompatibility
build_project_feature unless project_feature build_project_feature unless project_feature
access_level = Gitlab::Utils.to_boolean(value) ? ProjectFeature::ENABLED : ProjectFeature::DISABLED access_level = Gitlab::Utils.to_boolean(value) ? ProjectFeature::ENABLED : ProjectFeature::DISABLED
project_feature.update_attribute(field, access_level) project_feature.send(:write_attribute, field, access_level)
end end
end end
...@@ -55,30 +55,30 @@ module ReactiveCaching ...@@ -55,30 +55,30 @@ module ReactiveCaching
self.reactive_cache_refresh_interval = 1.minute self.reactive_cache_refresh_interval = 1.minute
self.reactive_cache_lifetime = 10.minutes self.reactive_cache_lifetime = 10.minutes
def calculate_reactive_cache def calculate_reactive_cache(*args)
raise NotImplementedError raise NotImplementedError
end end
def with_reactive_cache(&blk) def with_reactive_cache(*args, &blk)
within_reactive_cache_lifetime do within_reactive_cache_lifetime(*args) do
data = Rails.cache.read(full_reactive_cache_key) data = Rails.cache.read(full_reactive_cache_key(*args))
yield data if data.present? yield data if data.present?
end end
ensure ensure
Rails.cache.write(full_reactive_cache_key('alive'), true, expires_in: self.class.reactive_cache_lifetime) Rails.cache.write(alive_reactive_cache_key(*args), true, expires_in: self.class.reactive_cache_lifetime)
ReactiveCachingWorker.perform_async(self.class, id) ReactiveCachingWorker.perform_async(self.class, id, *args)
end end
def clear_reactive_cache! def clear_reactive_cache!(*args)
Rails.cache.delete(full_reactive_cache_key) Rails.cache.delete(full_reactive_cache_key(*args))
end end
def exclusively_update_reactive_cache! def exclusively_update_reactive_cache!(*args)
locking_reactive_cache do locking_reactive_cache(*args) do
within_reactive_cache_lifetime do within_reactive_cache_lifetime(*args) do
enqueuing_update do enqueuing_update(*args) do
value = calculate_reactive_cache value = calculate_reactive_cache(*args)
Rails.cache.write(full_reactive_cache_key, value) Rails.cache.write(full_reactive_cache_key(*args), value)
end end
end end
end end
...@@ -93,22 +93,26 @@ module ReactiveCaching ...@@ -93,22 +93,26 @@ module ReactiveCaching
([prefix].flatten + qualifiers).join(':') ([prefix].flatten + qualifiers).join(':')
end end
def locking_reactive_cache def alive_reactive_cache_key(*qualifiers)
lease = Gitlab::ExclusiveLease.new(full_reactive_cache_key, timeout: reactive_cache_lease_timeout) full_reactive_cache_key(*(qualifiers + ['alive']))
end
def locking_reactive_cache(*args)
lease = Gitlab::ExclusiveLease.new(full_reactive_cache_key(*args), timeout: reactive_cache_lease_timeout)
uuid = lease.try_obtain uuid = lease.try_obtain
yield if uuid yield if uuid
ensure ensure
Gitlab::ExclusiveLease.cancel(full_reactive_cache_key, uuid) Gitlab::ExclusiveLease.cancel(full_reactive_cache_key(*args), uuid)
end end
def within_reactive_cache_lifetime def within_reactive_cache_lifetime(*args)
yield if Rails.cache.read(full_reactive_cache_key('alive')) yield if Rails.cache.read(alive_reactive_cache_key(*args))
end end
def enqueuing_update def enqueuing_update(*args)
yield yield
ensure ensure
ReactiveCachingWorker.perform_in(self.class.reactive_cache_refresh_interval, self.class, id) ReactiveCachingWorker.perform_in(self.class.reactive_cache_refresh_interval, self.class, id, *args)
end end
end end
end end
module ReactiveService
extend ActiveSupport::Concern
included do
include ReactiveCaching
# Default cache key: class name + project_id
self.reactive_cache_key = ->(service) { [ service.class.model_name.singular, service.project_id ] }
end
end
class BambooService < CiService class BambooService < CiService
include ReactiveService
prop_accessor :bamboo_url, :build_key, :username, :password prop_accessor :bamboo_url, :build_key, :username, :password
validates :bamboo_url, presence: true, url: true, if: :activated? validates :bamboo_url, presence: true, url: true, if: :activated?
...@@ -58,31 +60,46 @@ class BambooService < CiService ...@@ -58,31 +60,46 @@ class BambooService < CiService
%w(push) %w(push)
end end
def build_info(sha) def build_page(sha, ref)
@response = get_path("rest/api/latest/result?label=#{sha}") with_reactive_cache(sha, ref) {|cached| cached[:build_page] }
end end
def build_page(sha, ref) def commit_status(sha, ref)
build_info(sha) if @response.nil? || !@response.code with_reactive_cache(sha, ref) {|cached| cached[:commit_status] }
end
if @response.code != 200 || @response['results']['results']['size'] == '0' def execute(data)
return unless supported_events.include?(data[:object_kind])
get_path("updateAndBuild.action?buildKey=#{build_key}")
end
def calculate_reactive_cache(sha, ref)
response = get_path("rest/api/latest/result?label=#{sha}")
{ build_page: read_build_page(response), commit_status: read_commit_status(response) }
end
private
def read_build_page(response)
if response.code != 200 || response['results']['results']['size'] == '0'
# If actual build link can't be determined, send user to build summary page. # If actual build link can't be determined, send user to build summary page.
URI.join("#{bamboo_url}/", "browse/#{build_key}").to_s URI.join("#{bamboo_url}/", "browse/#{build_key}").to_s
else else
# If actual build link is available, go to build result page. # If actual build link is available, go to build result page.
result_key = @response['results']['results']['result']['planResultKey']['key'] result_key = response['results']['results']['result']['planResultKey']['key']
URI.join("#{bamboo_url}/", "browse/#{result_key}").to_s URI.join("#{bamboo_url}/", "browse/#{result_key}").to_s
end end
end end
def commit_status(sha, ref) def read_commit_status(response)
build_info(sha) if @response.nil? || !@response.code return :error unless response.code == 200 || response.code == 404
return :error unless @response.code == 200 || @response.code == 404
status = if @response.code == 404 || @response['results']['results']['size'] == '0' status = if response.code == 404 || response['results']['results']['size'] == '0'
'Pending' 'Pending'
else else
@response['results']['results']['result']['buildState'] response['results']['results']['result']['buildState']
end end
if status.include?('Success') if status.include?('Success')
...@@ -96,14 +113,6 @@ class BambooService < CiService ...@@ -96,14 +113,6 @@ class BambooService < CiService
end end
end end
def execute(data)
return unless supported_events.include?(data[:object_kind])
get_path("updateAndBuild.action?buildKey=#{build_key}")
end
private
def build_url(path) def build_url(path)
URI.join("#{bamboo_url}/", path).to_s URI.join("#{bamboo_url}/", path).to_s
end end
......
require "addressable/uri" require "addressable/uri"
class BuildkiteService < CiService class BuildkiteService < CiService
include ReactiveService
ENDPOINT = "https://buildkite.com" ENDPOINT = "https://buildkite.com"
prop_accessor :project_url, :token prop_accessor :project_url, :token
...@@ -33,13 +35,7 @@ class BuildkiteService < CiService ...@@ -33,13 +35,7 @@ class BuildkiteService < CiService
end end
def commit_status(sha, ref) def commit_status(sha, ref)
response = HTTParty.get(commit_status_path(sha), verify: false) with_reactive_cache(sha, ref) {|cached| cached[:commit_status] }
if response.code == 200 && response['status']
response['status']
else
:error
end
end end
def commit_status_path(sha) def commit_status_path(sha)
...@@ -78,6 +74,19 @@ class BuildkiteService < CiService ...@@ -78,6 +74,19 @@ class BuildkiteService < CiService
] ]
end end
def calculate_reactive_cache(sha, ref)
response = HTTParty.get(commit_status_path(sha), verify: false)
status =
if response.code == 200 && response['status']
response['status']
else
:error
end
{ commit_status: status }
end
private private
def webhook_token def webhook_token
......
...@@ -12,15 +12,7 @@ class CiService < Service ...@@ -12,15 +12,7 @@ class CiService < Service
%w(push) %w(push)
end end
def merge_request_page(iid, sha, ref) # Return complete url to build page
commit_page(sha, ref)
end
def commit_page(sha, ref)
build_page(sha, ref)
end
# Return complete url to merge_request page
# #
# Ex. # Ex.
# http://jenkins.example.com:8888/job/test1/scm/bySHA1/12d65c # http://jenkins.example.com:8888/job/test1/scm/bySHA1/12d65c
...@@ -29,23 +21,6 @@ class CiService < Service ...@@ -29,23 +21,6 @@ class CiService < Service
# implement inside child # implement inside child
end end
# Return string with build status or :error symbol
#
# Allowed states: 'success', 'failed', 'running', 'pending', 'skipped'
#
#
# Ex.
# @service.merge_request_status(9, '13be4ac', 'dev')
# # => 'success'
#
# @service.merge_request_status(10, '2abe4ac', 'dev)
# # => 'running'
#
#
def merge_request_status(iid, sha, ref)
commit_status(sha, ref)
end
# Return string with build status or :error symbol # Return string with build status or :error symbol
# #
# Allowed states: 'success', 'failed', 'running', 'pending', 'skipped' # Allowed states: 'success', 'failed', 'running', 'pending', 'skipped'
......
class DroneCiService < CiService class DroneCiService < CiService
include ReactiveService
prop_accessor :drone_url, :token prop_accessor :drone_url, :token
boolean_accessor :enable_ssl_verification boolean_accessor :enable_ssl_verification
...@@ -34,14 +36,6 @@ class DroneCiService < CiService ...@@ -34,14 +36,6 @@ class DroneCiService < CiService
%w(push merge_request tag_push) %w(push merge_request tag_push)
end end
def merge_request_status_path(iid, sha = nil, ref = nil)
url = [drone_url,
"gitlab/#{project.namespace.path}/#{project.path}/pulls/#{iid}",
"?access_token=#{token}"]
URI.join(*url).to_s
end
def commit_status_path(sha, ref) def commit_status_path(sha, ref)
url = [drone_url, url = [drone_url,
"gitlab/#{project.namespace.path}/#{project.path}/commits/#{sha}", "gitlab/#{project.namespace.path}/#{project.path}/commits/#{sha}",
...@@ -50,29 +44,14 @@ class DroneCiService < CiService ...@@ -50,29 +44,14 @@ class DroneCiService < CiService
URI.join(*url).to_s URI.join(*url).to_s
end end
def merge_request_status(iid, sha, ref) def commit_status(sha, ref)
response = HTTParty.get(merge_request_status_path(iid), verify: enable_ssl_verification) with_reactive_cache(sha, ref) {|cached| cached[:commit_status] }
if response.code == 200 and response['status']
case response['status']
when 'killed'
:canceled
when 'failure', 'error'
# Because drone return error if some test env failed
:failed
else
response["status"]
end
else
:error
end
rescue Errno::ECONNREFUSED
:error
end end
def commit_status(sha, ref) def calculate_reactive_cache(sha, ref)
response = HTTParty.get(commit_status_path(sha, ref), verify: enable_ssl_verification) response = HTTParty.get(commit_status_path(sha, ref), verify: enable_ssl_verification)
status =
if response.code == 200 and response['status'] if response.code == 200 and response['status']
case response['status'] case response['status']
when 'killed' when 'killed'
...@@ -86,18 +65,13 @@ class DroneCiService < CiService ...@@ -86,18 +65,13 @@ class DroneCiService < CiService
else else
:error :error
end end
rescue Errno::ECONNREFUSED
:error
end
def merge_request_page(iid, sha, ref)
url = [drone_url,
"gitlab/#{project.namespace.path}/#{project.path}/redirect/pulls/#{iid}"]
URI.join(*url).to_s { commit_status: status }
rescue Errno::ECONNREFUSED
{ commit_status: :error }
end end
def commit_page(sha, ref) def build_page(sha, ref)
url = [drone_url, url = [drone_url,
"gitlab/#{project.namespace.path}/#{project.path}/redirect/commits/#{sha}", "gitlab/#{project.namespace.path}/#{project.path}/redirect/commits/#{sha}",
"?branch=#{URI::encode(ref.to_s)}"] "?branch=#{URI::encode(ref.to_s)}"]
...@@ -105,14 +79,6 @@ class DroneCiService < CiService ...@@ -105,14 +79,6 @@ class DroneCiService < CiService
URI.join(*url).to_s URI.join(*url).to_s
end end
def commit_coverage(sha, ref)
nil
end
def build_page(sha, ref)
commit_page(sha, ref)
end
def title def title
'Drone CI' 'Drone CI'
end end
......
class TeamcityService < CiService class TeamcityService < CiService
include ReactiveService
prop_accessor :teamcity_url, :build_type, :username, :password prop_accessor :teamcity_url, :build_type, :username, :password
validates :teamcity_url, presence: true, url: true, if: :activated? validates :teamcity_url, presence: true, url: true, if: :activated?
...@@ -61,43 +63,18 @@ class TeamcityService < CiService ...@@ -61,43 +63,18 @@ class TeamcityService < CiService
] ]
end end
def build_info(sha)
@response = get_path("httpAuth/app/rest/builds/branch:unspecified:any,number:#{sha}")
end
def build_page(sha, ref) def build_page(sha, ref)
build_info(sha) if @response.nil? || !@response.code with_reactive_cache(sha, ref) {|cached| cached[:build_page] }
if @response.code != 200
# If actual build link can't be determined,
# send user to build summary page.
build_url("viewLog.html?buildTypeId=#{build_type}")
else
# If actual build link is available, go to build result page.
built_id = @response['build']['id']
build_url("viewLog.html?buildId=#{built_id}&buildTypeId=#{build_type}")
end
end end
def commit_status(sha, ref) def commit_status(sha, ref)
build_info(sha) if @response.nil? || !@response.code with_reactive_cache(sha, ref) {|cached| cached[:commit_status] }
return :error unless @response.code == 200 || @response.code == 404
status = if @response.code == 404
'Pending'
else
@response['build']['status']
end end
if status.include?('SUCCESS') def calculate_reactive_cache(sha, ref)
'success' response = get_path("httpAuth/app/rest/builds/branch:unspecified:any,number:#{sha}")
elsif status.include?('FAILURE')
'failed' { build_page: read_build_page(response), commit_status: read_commit_status(response) }
elsif status.include?('Pending')
'pending'
else
:error
end
end end
def execute(data) def execute(data)
...@@ -122,6 +99,40 @@ class TeamcityService < CiService ...@@ -122,6 +99,40 @@ class TeamcityService < CiService
private private
def read_build_page(response)
if response.code != 200
# If actual build link can't be determined,
# send user to build summary page.
build_url("viewLog.html?buildTypeId=#{build_type}")
else
# If actual build link is available, go to build result page.
built_id = response['build']['id']
build_url("viewLog.html?buildId=#{built_id}&buildTypeId=#{build_type}")
end
end
def read_commit_status(response)
return :error unless response.code == 200 || response.code == 404
status = if response.code == 404
'Pending'
else
response['build']['status']
end
return :error unless status.present?
if status.include?('SUCCESS')
'success'
elsif status.include?('FAILURE')
'failed'
elsif status.include?('Pending')
'pending'
else
:error
end
end
def build_url(path) def build_url(path)
URI.join("#{teamcity_url}/", path).to_s URI.join("#{teamcity_url}/", path).to_s
end end
......
...@@ -8,16 +8,16 @@ class CommitEntity < API::Entities::RepoCommit ...@@ -8,16 +8,16 @@ class CommitEntity < API::Entities::RepoCommit
end end
expose :commit_url do |commit| expose :commit_url do |commit|
namespace_project_tree_url( namespace_project_commit_url(
request.project.namespace, request.project.namespace,
request.project, request.project,
id: commit.id) commit)
end end
expose :commit_path do |commit| expose :commit_path do |commit|
namespace_project_tree_path( namespace_project_commit_path(
request.project.namespace, request.project.namespace,
request.project, request.project,
id: commit.id) commit)
end end
end end
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
Your New Personal Access Token Your New Personal Access Token
.form-group .form-group
= text_field_tag 'created-personal-access-token', flash[:personal_access_token], readonly: true, class: "form-control", 'aria-describedby' => "created-personal-access-token-help-block" = text_field_tag 'created-personal-access-token', flash[:personal_access_token], readonly: true, class: "form-control", 'aria-describedby' => "created-personal-access-token-help-block"
= clipboard_button(clipboard_text: flash[:personal_access_token]) = clipboard_button(clipboard_text: flash[:personal_access_token], title: "Copy personal access token to clipboard", placement: "left")
%span#created-personal-access-token-help-block.help-block.text-danger Make sure you save it - you won't be able to access it again. %span#created-personal-access-token-help-block.help-block.text-danger Make sure you save it - you won't be able to access it again.
%hr %hr
......
...@@ -7,20 +7,21 @@ ...@@ -7,20 +7,21 @@
%p %p
= @teams.one? ? 'The team' : 'Select the team' = @teams.one? ? 'The team' : 'Select the team'
where the slash commands will be used in where the slash commands will be used in
- selected_id = @teams.keys.first if @teams.one? - selected_id = @teams.one? ? @teams.keys.first : 0
- options = mattermost_teams_options(@teams) - options = mattermost_teams_options(@teams)
- options = options_for_select(options, selected_id) - options = options_for_select(options, selected_id)
= f.select(:team_id, options, {}, { class: 'form-control', selected: "#{selected_id}" }) = f.select(:team_id, options, {}, { class: 'form-control', disabled: @teams.one?, selected: selected_id })
= f.hidden_field(:team_id, value: selected_id) if @teams.one?
.help-block .help-block
- if @teams.one? - if @teams.one?
This is the only team where you are an administrator. This is the only available team.
- else - else
The list shows teams where you are administrator The list shows all available teams.
To create a team, ask your Mattermost system administrator.
To create a team, To create a team,
= link_to "#{Gitlab.config.mattermost.host}/create_team" do = link_to "#{Gitlab.config.mattermost.host}/create_team" do
use Mattermost's interface use Mattermost's interface
= icon('external-link') = icon('external-link')
or ask your Mattermost system administrator.
%hr %hr
%h4 Command trigger word %h4 Command trigger word
%p Choose the word that will trigger commands %p Choose the word that will trigger commands
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
%p %p
%strong Step 1. %strong Step 1.
Fetch and check out the branch for this merge request Fetch and check out the branch for this merge request
= clipboard_button(clipboard_target: "pre#merge-info-1") = clipboard_button(clipboard_target: "pre#merge-info-1", title: "Copy commands to clipboard")
%pre.dark#merge-info-1 %pre.dark#merge-info-1
- if @merge_request.for_fork? - if @merge_request.for_fork?
:preserve :preserve
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
%p %p
%strong Step 3. %strong Step 3.
Merge the branch and fix any conflicts that come up Merge the branch and fix any conflicts that come up
= clipboard_button(clipboard_target: "pre#merge-info-3") = clipboard_button(clipboard_target: "pre#merge-info-3", title: "Copy commands to clipboard")
%pre.dark#merge-info-3 %pre.dark#merge-info-3
- if @merge_request.for_fork? - if @merge_request.for_fork?
:preserve :preserve
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
%p %p
%strong Step 4. %strong Step 4.
Push the result of the merge to GitLab Push the result of the merge to GitLab
= clipboard_button(clipboard_target: "pre#merge-info-4") = clipboard_button(clipboard_target: "pre#merge-info-4", title: "Copy commands to clipboard")
%pre.dark#merge-info-4 %pre.dark#merge-info-4
:preserve :preserve
git push origin #{h @merge_request.target_branch} git push origin #{h @merge_request.target_branch}
......
- stage = local_assigns.fetch(:stage) - stage = local_assigns.fetch(:stage)
- statuses = stage.statuses.latest - statuses = stage.statuses.latest
- status_groups = statuses.sort_by(&:name).group_by(&:group_name) - status_groups = statuses.sort_by(&:sortable_name).group_by(&:group_name)
%li.stage-column %li.stage-column
.stage-name .stage-name
%a{ name: stage.name } %a{ name: stage.name }
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
= text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true = text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true
.input-group-btn .input-group-btn
= clipboard_button(clipboard_target: '#project_clone') = clipboard_button(clipboard_target: '#project_clone', title: "Copy URL to clipboard")
:javascript :javascript
$('ul.clone-options-dropdown a').on('click',function(e){ $('ul.clone-options-dropdown a').on('click',function(e){
......
...@@ -153,13 +153,13 @@ ...@@ -153,13 +153,13 @@
- project_ref = cross_project_reference(@project, issuable) - project_ref = cross_project_reference(@project, issuable)
.block.project-reference .block.project-reference
.sidebar-collapsed-icon.dont-change-state .sidebar-collapsed-icon.dont-change-state
= clipboard_button(clipboard_text: project_ref) = clipboard_button(clipboard_text: project_ref, title: "Copy reference to clipboard", placement: "left")
.cross-project-reference.hide-collapsed .cross-project-reference.hide-collapsed
%span %span
Reference: Reference:
%cite{ title: project_ref } %cite{ title: project_ref }
= project_ref = project_ref
= clipboard_button(clipboard_text: project_ref) = clipboard_button(clipboard_text: project_ref, title: "Copy reference to clipboard", placement: "left")
:javascript :javascript
new MilestoneSelect('{"namespace":"#{@project.namespace.path}","path":"#{@project.path}"}'); new MilestoneSelect('{"namespace":"#{@project.namespace.path}","path":"#{@project.path}"}');
......
...@@ -2,7 +2,7 @@ class ReactiveCachingWorker ...@@ -2,7 +2,7 @@ class ReactiveCachingWorker
include Sidekiq::Worker include Sidekiq::Worker
include DedicatedSidekiqQueue include DedicatedSidekiqQueue
def perform(class_name, id) def perform(class_name, id, *args)
klass = begin klass = begin
Kernel.const_get(class_name) Kernel.const_get(class_name)
rescue NameError rescue NameError
...@@ -10,6 +10,6 @@ class ReactiveCachingWorker ...@@ -10,6 +10,6 @@ class ReactiveCachingWorker
end end
return unless klass return unless klass
klass.find_by(id: id).try(:exclusively_update_reactive_cache!) klass.find_by(id: id).try(:exclusively_update_reactive_cache!, *args)
end end
end end
---
title: Query external CI statuses in the background
merge_request:
author:
---
title: Check for env[Grape::Env::GRAPE_ROUTING_ARGS] instead of endpoint.route
merge_request: 8544
author:
---
title: Fixes pipeline status cell is too wide by adding missing classes in table head cells
merge_request: 8549
author:
---
title: Search bar redesign first iteration
merge_request: 7345
author:
---
title: Mutate the attribute instead of issuing a write operation to the DB in `ProjectFeaturesCompatibility`
concern.
merge_request: 8552
author:
---
title: 'Copy <some text> to clipboard'
merge_request: 8535
---
title: Allow to use ENV variables in redis config
merge_request: 8073
author: Semyon Pupkov
---
title: Sort numbers in build names more intelligently
merge_request: 8277
author:
---
title: 'API: fix query response for `/projects/:id/issues?milestone="No%20Milestone"`'
merge_request: 8457
author: Panagiotis Atmatzidis, David Eisner
---
title: Fix links to commits pages on pipelines list page
merge_request: 8558
author:
...@@ -14,9 +14,8 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration ...@@ -14,9 +14,8 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration
namespace_id = user['namespace_id'] namespace_id = user['namespace_id']
path_was = user['username'] path_was = user['username']
path_was_wildcard = quote_string("#{path_was}/%") path_was_wildcard = quote_string("#{path_was}/%")
path = quote_string(rename_path(path_was))
move_namespace(namespace_id, path_was, path) path = move_namespace(namespace_id, path_was, path)
execute "UPDATE routes SET path = '#{path}' WHERE source_type = 'Namespace' AND source_id = #{namespace_id}" execute "UPDATE routes SET path = '#{path}' WHERE source_type = 'Namespace' AND source_id = #{namespace_id}"
execute "UPDATE namespaces SET path = '#{path}' WHERE id = #{namespace_id}" execute "UPDATE namespaces SET path = '#{path}' WHERE id = #{namespace_id}"
...@@ -45,9 +44,13 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration ...@@ -45,9 +44,13 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration
select_all("SELECT id, path FROM routes WHERE path = '#{quote_string(path)}'").present? select_all("SELECT id, path FROM routes WHERE path = '#{quote_string(path)}'").present?
end end
def path_exists?(repository_storage_path, path)
gitlab_shell.exists?(repository_storage_path, path)
end
# Accepts invalid path like test.git and returns test_git or # Accepts invalid path like test.git and returns test_git or
# test_git1 if test_git already taken # test_git1 if test_git already taken
def rename_path(path) def rename_path(repository_storage_path, path)
# To stay closer with original name and reduce risk of duplicates # To stay closer with original name and reduce risk of duplicates
# we rename suffix instead of removing it # we rename suffix instead of removing it
path = path.sub(/\.git\z/, '_git') path = path.sub(/\.git\z/, '_git')
...@@ -55,7 +58,7 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration ...@@ -55,7 +58,7 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration
counter = 0 counter = 0
base = path base = path
while route_exists?(path) while route_exists?(path) || path_exists?(repository_storage_path, path)
counter += 1 counter += 1
path = "#{base}#{counter}" path = "#{base}#{counter}"
end end
...@@ -73,6 +76,8 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration ...@@ -73,6 +76,8 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration
# Ensure old directory exists before moving it # Ensure old directory exists before moving it
gitlab_shell.add_namespace(repository_storage_path, path_was) gitlab_shell.add_namespace(repository_storage_path, path_was)
path = quote_string(rename_path(repository_storage_path, path_was))
unless gitlab_shell.mv_namespace(repository_storage_path, path_was, path) unless gitlab_shell.mv_namespace(repository_storage_path, path_was, path)
Rails.logger.error "Exception moving path #{repository_storage_path} from #{path_was} to #{path}" Rails.logger.error "Exception moving path #{repository_storage_path} from #{path_was} to #{path}"
...@@ -83,5 +88,7 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration ...@@ -83,5 +88,7 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration
end end
Gitlab::UploadsTransfer.new.rename_namespace(path_was, path) Gitlab::UploadsTransfer.new.rename_namespace(path_was, path)
path
end end
end end
...@@ -23,12 +23,15 @@ GET /issues?state=closed ...@@ -23,12 +23,15 @@ GET /issues?state=closed
GET /issues?labels=foo GET /issues?labels=foo
GET /issues?labels=foo,bar GET /issues?labels=foo,bar
GET /issues?labels=foo,bar&state=opened GET /issues?labels=foo,bar&state=opened
GET /issues?milestone=1.0.0
GET /issues?milestone=1.0.0&state=opened
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `state` | string | no | Return all issues or just those that are `opened` or `closed`| | `state` | string | no | Return all issues or just those that are `opened` or `closed`|
| `labels` | string | no | Comma-separated list of label names, issues with any of the labels will be returned | | `labels` | string | no | Comma-separated list of label names, issues with any of the labels will be returned |
| `milestone` | string| no | The milestone title |
| `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` | | `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` | | `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
......
...@@ -601,6 +601,12 @@ If you want to connect the Redis server via socket, then use the "unix:" URL sch ...@@ -601,6 +601,12 @@ If you want to connect the Redis server via socket, then use the "unix:" URL sch
production: production:
url: unix:/path/to/redis/socket url: unix:/path/to/redis/socket
Also you can use environment variables in the `config/resque.yml` file:
# example
production:
url: <%= ENV.fetch('GITLAB_REDIS_URL') %>
### Custom SSH Connection ### Custom SSH Connection
If you are running SSH on a non-standard port, you must change the GitLab user's SSH config. If you are running SSH on a non-standard port, you must change the GitLab user's SSH config.
......
...@@ -6,7 +6,7 @@ Slack commands give users an extra interface to perform common operations ...@@ -6,7 +6,7 @@ Slack commands give users an extra interface to perform common operations
from the chat environment. This allows one to, for example, create an issue as from the chat environment. This allows one to, for example, create an issue as
soon as the idea was discussed in chat. soon as the idea was discussed in chat.
For all available commands try the help subcommand, for example: `/gitlab help`, For all available commands try the help subcommand, for example: `/gitlab help`,
all review the [full list of commands](../integrations/chat_commands.md). all review the [full list of commands](../integration/chat_commands.md).
## Prerequisites ## Prerequisites
......
@admin
Feature: Admin Users
Background:
Given I sign in as an admin
And system has users
Scenario: On Admin Users
Given I visit admin users page
Then I should see all users
Scenario: Edit user and change username to non ascii char
When I visit admin users page
And Click edit
And Input non ascii char in username
And Click save
Then See username error message
And Not changed form action url
Scenario: Show user attributes
Given user "Mike" with groups and projects
Given I visit admin users page
And click on "Mike" link
Then I should see user "Mike" details
Scenario: Edit my user attributes
Given I visit admin users page
And click edit on my user
When I submit modified user
Then I see user attributes changed
@javascript
Scenario: Remove users secondary email
Given I visit admin users page
And I view the user with secondary email
And I see the secondary email
When I click remove secondary email
Then I should not see secondary email anymore
Scenario: Show user keys
Given user "Pete" with ssh keys
And I visit admin users page
And click on user "Pete"
And click on ssh keys tab
Then I should see key list
And I click on the key title
Then I should see key details
And I click on remove key
Then I should see the key removed
Scenario: Show user identities
Given user "Pete" with twitter account
And I visit "Pete" identities page in admin
Then I should see twitter details
Scenario: Update user identities
Given user "Pete" with twitter account
And I visit "Pete" identities page in admin
And I modify twitter identity
Then I should see twitter details updated
Scenario: Remove user identities
Given user "Pete" with twitter account
And I visit "Pete" identities page in admin
And I remove twitter identity
Then I should not see twitter details
@dashboard
Feature: Dashboard Active Tab
Background:
Given I sign in as a user
Scenario: On Dashboard Home
Given I visit dashboard page
Then the active main tab should be Home
And no other main tabs should be active
Scenario: On Dashboard Issues
Given I visit dashboard issues page
Then the active main tab should be Issues
And no other main tabs should be active
Scenario: On Dashboard Merge Requests
Given I visit dashboard merge requests page
Then the active main tab should be Merge Requests
And no other main tabs should be active
Scenario: On Dashboard Groups
Given I visit dashboard groups page
Then the active main tab should be Groups
And no other main tabs should be active
@dashboard
Feature: Dashboard Archived Projects
Background:
Given I sign in as a user
And I own project "Shop"
And I own project "Forum"
And project "Forum" is archived
And I visit dashboard page
Scenario: I should see non-archived projects on dashboard
Then I should see "Shop" project link
And I should not see "Forum" project link
Scenario: I toggle show of archived projects on dashboard
When I click "Show archived projects" link
Then I should see "Shop" project link
And I should see "Forum" project link
@dashboard
Feature: Dashboard Group
Background:
Given I sign in as "John Doe"
And "John Doe" is owner of group "Owned"
And "John Doe" is guest of group "Guest"
Scenario: Create a group from dasboard
And I visit dashboard groups page
And I click new group link
And submit form with new group "Samurai" info
Then I should be redirected to group "Samurai" page
And I should see newly created group "Samurai"
@dashboard
Feature: Dashboard Help
Background:
Given I sign in as a user
And I visit the "Rake Tasks" help page
Scenario: The markdown should be rendered correctly
Then I should see "Rake Tasks" page markdown rendered
And Header "Rebuild project satellites" should have correct ids and links
class Spinach::Features::AdminUsers < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedAdmin
before do
allow(Gitlab::OAuth::Provider).to receive(:providers).and_return([:twitter, :twitter_updated])
allow_any_instance_of(ApplicationHelper).to receive(:user_omniauth_authorize_path).and_return(root_path)
end
after do
allow(Gitlab::OAuth::Provider).to receive(:providers).and_call_original
allow_any_instance_of(ApplicationHelper).to receive(:user_omniauth_authorize_path).and_call_original
end
step 'I should see all users' do
User.all.each do |user|
expect(page).to have_content user.name
end
end
step 'Click edit' do
@user = User.first
find("#edit_user_#{@user.id}").click
end
step 'Input non ascii char in username' do
fill_in 'user_username', with: "\u3042\u3044"
end
step 'Click save' do
click_button("Save")
end
step 'See username error message' do
page.within "#error_explanation" do
expect(page).to have_content "Username"
end
end
step 'Not changed form action url' do
expect(page).to have_selector %(form[action="/admin/users/#{@user.username}"])
end
step 'I submit modified user' do
check :user_can_create_group
click_button 'Save'
end
step 'I see user attributes changed' do
expect(page).to have_content 'Can create groups: Yes'
end
step 'click edit on my user' do
find("#edit_user_#{current_user.id}").click
end
step 'I view the user with secondary email' do
@user_with_secondary_email = User.last
@user_with_secondary_email.emails.new(email: "secondary@example.com")
@user_with_secondary_email.save
visit "/admin/users/#{@user_with_secondary_email.username}"
end
step 'I see the secondary email' do
expect(page).to have_content "Secondary email: #{@user_with_secondary_email.emails.last.email}"
end
step 'I click remove secondary email' do
find("#remove_email_#{@user_with_secondary_email.emails.last.id}").click
end
step 'I should not see secondary email anymore' do
expect(page).not_to have_content "Secondary email:"
end
step 'user "Mike" with groups and projects' do
user = create(:user, name: 'Mike')
project = create(:empty_project)
project.team << [user, :developer]
group = create(:group)
group.add_developer(user)
end
step 'click on "Mike" link' do
click_link "Mike"
end
step 'I should see user "Mike" details' do
expect(page).to have_content 'Account'
expect(page).to have_content 'Personal projects limit'
end
step 'user "Pete" with ssh keys' do
user = create(:user, name: 'Pete')
create(:key, user: user, title: "ssh-rsa Key1", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4FIEBXGi4bPU8kzxMefudPIJ08/gNprdNTaO9BR/ndy3+58s2HCTw2xCHcsuBmq+TsAqgEidVq4skpqoTMB+Uot5Uzp9z4764rc48dZiI661izoREoKnuRQSsRqUTHg5wrLzwxlQbl1MVfRWQpqiz/5KjBC7yLEb9AbusjnWBk8wvC1bQPQ1uLAauEA7d836tgaIsym9BrLsMVnR4P1boWD3Xp1B1T/ImJwAGHvRmP/ycIqmKdSpMdJXwxcb40efWVj0Ibbe7ii9eeoLdHACqevUZi6fwfbymdow+FeqlkPoHyGg3Cu4vD/D8+8cRc7mE/zGCWcQ15Var83Tczour Key1")
create(:key, user: user, title: "ssh-rsa Key2", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQSTWXhJAX/He+nG78MiRRRn7m0Pb0XbcgTxE0etArgoFoh9WtvDf36HG6tOSg/0UUNcp0dICsNAmhBKdncp6cIyPaXJTURPRAGvhI0/VDk4bi27bRnccGbJ/hDaUxZMLhhrzY0r22mjVf8PF6dvv5QUIQVm1/LeaWYsHHvLgiIjwrXirUZPnFrZw6VLREoBKG8uWvfSXw1L5eapmstqfsME8099oi+vWLR8MgEysZQmD28M73fgW4zek6LDQzKQyJx9nB+hJkKUDvcuziZjGmRFlNgSA2mguERwL1OXonD8WYUrBDGKroIvBT39zS5d9tQDnidEJZ9Y8gv5ViYP7x Key2")
end
step 'click on user "Pete"' do
click_link 'Pete'
end
step 'I should see key list' do
expect(page).to have_content 'ssh-rsa Key2'
expect(page).to have_content 'ssh-rsa Key1'
end
step 'I click on the key title' do
click_link 'ssh-rsa Key2'
end
step 'I should see key details' do
expect(page).to have_content 'ssh-rsa Key2'
expect(page).to have_content 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQSTWXhJAX/He+nG78MiRRRn7m0Pb0XbcgTxE0etArgoFoh9WtvDf36HG6tOSg/0UUNcp0dICsNAmhBKdncp6cIyPaXJTURPRAGvhI0/VDk4bi27bRnccGbJ/hDaUxZMLhhrzY0r22mjVf8PF6dvv5QUIQVm1/LeaWYsHHvLgiIjwrXirUZPnFrZw6VLREoBKG8uWvfSXw1L5eapmstqfsME8099oi+vWLR8MgEysZQmD28M73fgW4zek6LDQzKQyJx9nB+hJkKUDvcuziZjGmRFlNgSA2mguERwL1OXonD8WYUrBDGKroIvBT39zS5d9tQDnidEJZ9Y8gv5ViYP7x Key2'
end
step 'I click on remove key' do
click_link 'Remove'
end
step 'I should see the key removed' do
expect(page).not_to have_content 'ssh-rsa Key2'
end
step 'user "Pete" with twitter account' do
@user = create(:user, name: 'Pete')
@user.identities.create!(extern_uid: '123456', provider: 'twitter')
end
step 'I visit "Pete" identities page in admin' do
visit admin_user_identities_path(@user)
end
step 'I should see twitter details' do
expect(page).to have_content 'Pete'
expect(page).to have_content 'twitter'
end
step 'I modify twitter identity' do
find('.table').find(:link, 'Edit').click
fill_in 'identity_extern_uid', with: '654321'
select 'twitter_updated', from: 'identity_provider'
click_button 'Save changes'
end
step 'I should see twitter details updated' do
expect(page).to have_content 'Pete'
expect(page).to have_content 'twitter_updated'
expect(page).to have_content '654321'
end
step 'I remove twitter identity' do
click_link 'Delete'
end
step 'I should not see twitter details' do
expect(page).to have_content 'Pete'
expect(page).not_to have_content 'twitter'
end
step 'click on ssh keys tab' do
click_link 'SSH keys'
end
end
class Spinach::Features::DashboardActiveTab < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedSidebarActiveTab
end
class Spinach::Features::DashboardArchivedProjects < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedProject
When 'project "Forum" is archived' do
project = Project.find_by(name: "Forum")
project.update_attribute(:archived, true)
end
step 'I should see "Shop" project link' do
expect(page).to have_link "Shop"
end
step 'I should not see "Forum" project link' do
expect(page).not_to have_link "Forum"
end
step 'I should see "Forum" project link' do
expect(page).to have_link "Forum"
end
step 'I click "Show archived projects" link' do
click_link "Show archived projects"
end
end
class Spinach::Features::DashboardGroup < Spinach::FeatureSteps
include SharedAuthentication
include SharedGroup
include SharedPaths
include SharedUser
step 'I click new group link' do
click_link "New Group"
end
step 'submit form with new group "Samurai" info' do
fill_in 'group_path', with: 'Samurai'
fill_in 'group_description', with: 'Tokugawa Shogunate'
click_button "Create group"
end
step 'I should be redirected to group "Samurai" page' do
expect(current_path).to eq group_path(Group.find_by(name: 'Samurai'))
end
step 'I should see newly created group "Samurai"' do
expect(page).to have_content "Samurai"
expect(page).to have_content "Tokugawa Shogunate"
end
end
class Spinach::Features::DashboardHelp < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedMarkdown
step 'I visit the help page' do
visit help_path
end
step 'I visit the "Rake Tasks" help page' do
visit help_page_path("administration/raketasks/maintenance")
end
step 'I should see "Rake Tasks" page markdown rendered' do
expect(page).to have_content "Gather information about GitLab and the system it runs on"
end
step 'Header "Rebuild project satellites" should have correct ids and links' do
header_should_have_correct_id_and_link(2, 'Check GitLab configuration', 'check-gitlab-configuration', '.documentation')
end
end
...@@ -5,13 +5,31 @@ module API ...@@ -5,13 +5,31 @@ module API
before { authenticate! } before { authenticate! }
helpers do helpers do
# TODO: Remove in 9.0 and switch to IssueFinder-based label filtering def find_issues(args = {})
def filter_issues_labels(issues, labels) args = params.merge(args)
issues.includes(:labels).where('labels.title' => labels.split(','))
args.delete(:id)
args[:milestone_title] = args.delete(:milestone)
match_all_labels = args.delete(:match_all_labels)
labels = args.delete(:labels)
args[:label_name] = labels if match_all_labels
args[:search] = "#{Issue.reference_prefix}#{args.delete(:iid)}" if args.key?(:iid)
issues = IssuesFinder.new(current_user, args).execute.inc_notes_with_associations
# TODO: Remove in 9.0 pass `label_name: args.delete(:labels)` to IssuesFinder
if !match_all_labels && labels.present?
issues = issues.includes(:labels).where('labels.title' => labels.split(','))
end
issues.reorder(args[:order_by] => args[:sort])
end end
params :issues_params do params :issues_params do
optional :labels, type: String, desc: 'Comma-separated list of label names' optional :labels, type: String, desc: 'Comma-separated list of label names'
optional :milestone, type: String, desc: 'Milestone title'
optional :order_by, type: String, values: %w[created_at updated_at], default: 'created_at', optional :order_by, type: String, values: %w[created_at updated_at], default: 'created_at',
desc: 'Return issues ordered by `created_at` or `updated_at` fields.' desc: 'Return issues ordered by `created_at` or `updated_at` fields.'
optional :sort, type: String, values: %w[asc desc], default: 'desc', optional :sort, type: String, values: %w[asc desc], default: 'desc',
...@@ -40,9 +58,7 @@ module API ...@@ -40,9 +58,7 @@ module API
use :issues_params use :issues_params
end end
get do get do
issues = IssuesFinder.new(current_user, scope: 'all', author_id: current_user.id, state: params[:state]).execute.inc_notes_with_associations issues = find_issues(scope: 'authored')
issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil?
issues = issues.reorder(params[:order_by] => params[:sort])
present paginate(issues), with: Entities::Issue, current_user: current_user present paginate(issues), with: Entities::Issue, current_user: current_user
end end
...@@ -61,15 +77,10 @@ module API ...@@ -61,15 +77,10 @@ module API
use :issues_params use :issues_params
end end
get ":id/issues" do get ":id/issues" do
group = find_group!(params.delete(:id)) group = find_group!(params[:id])
params[:group_id] = group.id issues = find_issues(group_id: group.id, state: params[:state] || 'opened', match_all_labels: true)
params[:milestone_title] = params.delete(:milestone)
params[:label_name] = params.delete(:labels)
issues = IssuesFinder.new(current_user, params).execute
issues = issues.reorder(params[:order_by] => params[:sort])
present paginate(issues), with: Entities::Issue, current_user: current_user present paginate(issues), with: Entities::Issue, current_user: current_user
end end
end end
...@@ -84,17 +95,13 @@ module API ...@@ -84,17 +95,13 @@ module API
params do params do
optional :state, type: String, values: %w[opened closed all], default: 'all', optional :state, type: String, values: %w[opened closed all], default: 'all',
desc: 'Return opened, closed, or all issues' desc: 'Return opened, closed, or all issues'
optional :iid, type: Integer, desc: 'The IID of the issue' optional :iid, type: Integer, desc: 'Return the issue having the given `iid`'
use :issues_params use :issues_params
end end
get ":id/issues" do get ":id/issues" do
issues = IssuesFinder.new(current_user, project = find_project(params[:id])
project_id: user_project.id,
state: params[:state], issues = find_issues(project_id: project.id)
milestone_title: params[:milestone]).execute.inc_notes_with_associations
issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil?
issues = filter_by_iid(issues, params[:iid]) unless params[:iid].nil?
issues = issues.reorder(params[:order_by] => params[:sort])
present paginate(issues), with: Entities::Issue, current_user: current_user, project: user_project present paginate(issues), with: Entities::Issue, current_user: current_user, project: user_project
end end
......
...@@ -30,9 +30,9 @@ module Gitlab ...@@ -30,9 +30,9 @@ module Gitlab
return unless @branch_name return unless @branch_name
return unless project.protected_branch?(@branch_name) return unless project.protected_branch?(@branch_name)
if forced_push? && user_access.cannot_do_action?(:force_push_code_to_protected_branches) if forced_push?
return "You are not allowed to force push code to a protected branch on this project." return "You are not allowed to force push code to a protected branch on this project."
elsif Gitlab::Git.blank_ref?(@newrev) && user_access.cannot_do_action?(:remove_protected_branches) elsif Gitlab::Git.blank_ref?(@newrev)
return "You are not allowed to delete protected branches from this project." return "You are not allowed to delete protected branches from this project."
end end
......
...@@ -71,10 +71,17 @@ module Gitlab ...@@ -71,10 +71,17 @@ module Gitlab
def tag_endpoint(trans, env) def tag_endpoint(trans, env)
endpoint = env[ENDPOINT_KEY] endpoint = env[ENDPOINT_KEY]
# endpoint.route is nil in the case of a 405 response begin
if endpoint.route route = endpoint.route
path = endpoint_paths_cache[endpoint.route.request_method][endpoint.route.path] rescue
trans.action = "Grape##{endpoint.route.request_method} #{path}" # endpoint.route is calling env[Grape::Env::GRAPE_ROUTING_ARGS][:route_info]
# but env[Grape::Env::GRAPE_ROUTING_ARGS] is nil in the case of a 405 response
# so we're rescuing exceptions and bailing out
end
if route
path = endpoint_paths_cache[route.request_method][route.path]
trans.action = "Grape##{route.request_method} #{path}"
end end
end end
......
...@@ -42,7 +42,7 @@ module Gitlab ...@@ -42,7 +42,7 @@ module Gitlab
return @_raw_config if defined?(@_raw_config) return @_raw_config if defined?(@_raw_config)
begin begin
@_raw_config = File.read(CONFIG_FILE).freeze @_raw_config = ERB.new(File.read(CONFIG_FILE)).result.freeze
rescue Errno::ENOENT rescue Errno::ENOENT
@_raw_config = false @_raw_config = false
end end
......
...@@ -12,7 +12,7 @@ describe Dashboard::TodosController do ...@@ -12,7 +12,7 @@ describe Dashboard::TodosController do
end end
context 'when using pagination' do context 'when using pagination' do
let(:last_page) { user.todos.page().total_pages } let(:last_page) { user.todos.page.total_pages }
let!(:issues) { create_list(:issue, 2, project: project, assignee: user) } let!(:issues) { create_list(:issue, 2, project: project, assignee: user) }
before do before do
......
...@@ -24,6 +24,10 @@ FactoryGirl.define do ...@@ -24,6 +24,10 @@ FactoryGirl.define do
visibility_level Gitlab::VisibilityLevel::PRIVATE visibility_level Gitlab::VisibilityLevel::PRIVATE
end end
trait :archived do
archived true
end
trait :access_requestable do trait :access_requestable do
request_access_enabled true request_access_enabled true
end end
......
...@@ -3,7 +3,11 @@ require 'spec_helper' ...@@ -3,7 +3,11 @@ require 'spec_helper'
describe "Admin::Users", feature: true do describe "Admin::Users", feature: true do
include WaitForAjax include WaitForAjax
before { login_as :admin } let!(:user) do
create(:omniauth_user, provider: 'twitter', extern_uid: '123456')
end
let!(:current_user) { login_as :admin }
describe "GET /admin/users" do describe "GET /admin/users" do
before do before do
...@@ -15,8 +19,10 @@ describe "Admin::Users", feature: true do ...@@ -15,8 +19,10 @@ describe "Admin::Users", feature: true do
end end
it "has users list" do it "has users list" do
expect(page).to have_content(@user.email) expect(page).to have_content(current_user.email)
expect(page).to have_content(@user.name) expect(page).to have_content(current_user.name)
expect(page).to have_content(user.email)
expect(page).to have_content(user.name)
end end
describe 'Two-factor Authentication filters' do describe 'Two-factor Authentication filters' do
...@@ -40,8 +46,6 @@ describe "Admin::Users", feature: true do ...@@ -40,8 +46,6 @@ describe "Admin::Users", feature: true do
end end
it 'counts users who have not enabled 2FA' do it 'counts users who have not enabled 2FA' do
create(:user)
visit admin_users_path visit admin_users_path
page.within('.filter-two-factor-disabled small') do page.within('.filter-two-factor-disabled small') do
...@@ -50,8 +54,6 @@ describe "Admin::Users", feature: true do ...@@ -50,8 +54,6 @@ describe "Admin::Users", feature: true do
end end
it 'filters by users who have not enabled 2FA' do it 'filters by users who have not enabled 2FA' do
user = create(:user)
visit admin_users_path visit admin_users_path
click_link '2FA Disabled' click_link '2FA Disabled'
...@@ -110,10 +112,10 @@ describe "Admin::Users", feature: true do ...@@ -110,10 +112,10 @@ describe "Admin::Users", feature: true do
describe "GET /admin/users/:id" do describe "GET /admin/users/:id" do
it "has user info" do it "has user info" do
visit admin_users_path visit admin_users_path
click_link @user.name click_link user.name
expect(page).to have_content(@user.email) expect(page).to have_content(user.email)
expect(page).to have_content(@user.name) expect(page).to have_content(user.name)
end end
describe 'Impersonation' do describe 'Impersonation' do
...@@ -126,7 +128,7 @@ describe "Admin::Users", feature: true do ...@@ -126,7 +128,7 @@ describe "Admin::Users", feature: true do
end end
it 'does not show impersonate button for admin itself' do it 'does not show impersonate button for admin itself' do
visit admin_user_path(@user) visit admin_user_path(current_user)
expect(page).not_to have_content('Impersonate') expect(page).not_to have_content('Impersonate')
end end
...@@ -158,7 +160,7 @@ describe "Admin::Users", feature: true do ...@@ -158,7 +160,7 @@ describe "Admin::Users", feature: true do
it 'logs out of impersonated user back to original user' do it 'logs out of impersonated user back to original user' do
find(:css, 'li.impersonation a').click find(:css, 'li.impersonation a').click
expect(page.find(:css, '.header-user .profile-link')['data-user']).to eql(@user.username) expect(page.find(:css, '.header-user .profile-link')['data-user']).to eql(current_user.username)
end end
it 'is redirected back to the impersonated users page in the admin after stopping' do it 'is redirected back to the impersonated users page in the admin after stopping' do
...@@ -171,15 +173,15 @@ describe "Admin::Users", feature: true do ...@@ -171,15 +173,15 @@ describe "Admin::Users", feature: true do
describe 'Two-factor Authentication status' do describe 'Two-factor Authentication status' do
it 'shows when enabled' do it 'shows when enabled' do
@user.update_attribute(:otp_required_for_login, true) user.update_attribute(:otp_required_for_login, true)
visit admin_user_path(@user) visit admin_user_path(user)
expect_two_factor_status('Enabled') expect_two_factor_status('Enabled')
end end
it 'shows when disabled' do it 'shows when disabled' do
visit admin_user_path(@user) visit admin_user_path(user)
expect_two_factor_status('Disabled') expect_two_factor_status('Disabled')
end end
...@@ -194,9 +196,8 @@ describe "Admin::Users", feature: true do ...@@ -194,9 +196,8 @@ describe "Admin::Users", feature: true do
describe "GET /admin/users/:id/edit" do describe "GET /admin/users/:id/edit" do
before do before do
@simple_user = create(:user)
visit admin_users_path visit admin_users_path
click_link "edit_user_#{@simple_user.id}" click_link "edit_user_#{user.id}"
end end
it "has user edit page" do it "has user edit page" do
...@@ -220,39 +221,52 @@ describe "Admin::Users", feature: true do ...@@ -220,39 +221,52 @@ describe "Admin::Users", feature: true do
end end
it "changes user entry" do it "changes user entry" do
@simple_user.reload user.reload
expect(@simple_user.name).to eq('Big Bang') expect(user.name).to eq('Big Bang')
expect(@simple_user.is_admin?).to be_truthy expect(user.is_admin?).to be_truthy
expect(@simple_user.password_expires_at).to be <= Time.now expect(user.password_expires_at).to be <= Time.now
end
end
describe 'update username to non ascii char' do
it do
fill_in 'user_username', with: '\u3042\u3044'
click_button('Save')
page.within '#error_explanation' do
expect(page).to have_content('Username')
end
expect(page).to have_selector(%(form[action="/admin/users/#{user.username}"]))
end end
end end
end end
describe "GET /admin/users/:id/projects" do describe "GET /admin/users/:id/projects" do
let(:group) { create(:group) }
let!(:project) { create(:project, group: group) }
before do before do
@group = create(:group) group.add_developer(user)
@project = create(:project, group: @group)
@simple_user = create(:user)
@group.add_developer(@simple_user)
visit projects_admin_user_path(@simple_user) visit projects_admin_user_path(user)
end end
it "lists group projects" do it "lists group projects" do
within(:css, '.append-bottom-default + .panel') do within(:css, '.append-bottom-default + .panel') do
expect(page).to have_content 'Group projects' expect(page).to have_content 'Group projects'
expect(page).to have_link @group.name, admin_group_path(@group) expect(page).to have_link group.name, admin_group_path(group)
end end
end end
it 'allows navigation to the group details' do it 'allows navigation to the group details' do
within(:css, '.append-bottom-default + .panel') do within(:css, '.append-bottom-default + .panel') do
click_link @group.name click_link group.name
end end
within(:css, 'h3.page-title') do within(:css, 'h3.page-title') do
expect(page).to have_content "Group: #{@group.name}" expect(page).to have_content "Group: #{group.name}"
end end
expect(page).to have_content @project.name expect(page).to have_content project.name
end end
it 'shows the group access level' do it 'shows the group access level' do
...@@ -270,4 +284,99 @@ describe "Admin::Users", feature: true do ...@@ -270,4 +284,99 @@ describe "Admin::Users", feature: true do
expect(page).not_to have_selector('.group_member') expect(page).not_to have_selector('.group_member')
end end
end end
describe 'show user attributes' do
it do
visit admin_users_path
click_link user.name
expect(page).to have_content 'Account'
expect(page).to have_content 'Personal projects limit'
end
end
describe 'remove users secondary email', js: true do
let!(:secondary_email) do
create :email, email: 'secondary@example.com', user: user
end
it do
visit admin_user_path(user.username)
expect(page).to have_content("Secondary email: #{secondary_email.email}")
find("#remove_email_#{secondary_email.id}").click
expect(page).not_to have_content(secondary_email.email)
end
end
describe 'show user keys' do
let!(:key1) do
create(:key, user: user, title: "ssh-rsa Key1", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4FIEBXGi4bPU8kzxMefudPIJ08/gNprdNTaO9BR/ndy3+58s2HCTw2xCHcsuBmq+TsAqgEidVq4skpqoTMB+Uot5Uzp9z4764rc48dZiI661izoREoKnuRQSsRqUTHg5wrLzwxlQbl1MVfRWQpqiz/5KjBC7yLEb9AbusjnWBk8wvC1bQPQ1uLAauEA7d836tgaIsym9BrLsMVnR4P1boWD3Xp1B1T/ImJwAGHvRmP/ycIqmKdSpMdJXwxcb40efWVj0Ibbe7ii9eeoLdHACqevUZi6fwfbymdow+FeqlkPoHyGg3Cu4vD/D8+8cRc7mE/zGCWcQ15Var83Tczour Key1")
end
let!(:key2) do
create(:key, user: user, title: "ssh-rsa Key2", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQSTWXhJAX/He+nG78MiRRRn7m0Pb0XbcgTxE0etArgoFoh9WtvDf36HG6tOSg/0UUNcp0dICsNAmhBKdncp6cIyPaXJTURPRAGvhI0/VDk4bi27bRnccGbJ/hDaUxZMLhhrzY0r22mjVf8PF6dvv5QUIQVm1/LeaWYsHHvLgiIjwrXirUZPnFrZw6VLREoBKG8uWvfSXw1L5eapmstqfsME8099oi+vWLR8MgEysZQmD28M73fgW4zek6LDQzKQyJx9nB+hJkKUDvcuziZjGmRFlNgSA2mguERwL1OXonD8WYUrBDGKroIvBT39zS5d9tQDnidEJZ9Y8gv5ViYP7x Key2")
end
it do
visit admin_users_path
click_link user.name
click_link 'SSH keys'
expect(page).to have_content(key1.title)
expect(page).to have_content(key2.title)
click_link key2.title
expect(page).to have_content(key2.title)
expect(page).to have_content(key2.key)
click_link 'Remove'
expect(page).not_to have_content(key2.title)
end
end
describe 'show user identities' do
it 'shows user identities' do
visit admin_user_identities_path(user)
expect(page).to have_content(user.name)
expect(page).to have_content('twitter')
end
end
describe 'update user identities' do
before do
allow(Gitlab::OAuth::Provider).to receive(:providers).and_return([:twitter, :twitter_updated])
end
it 'modifies twitter identity' do
visit admin_user_identities_path(user)
find('.table').find(:link, 'Edit').click
fill_in 'identity_extern_uid', with: '654321'
select 'twitter_updated', from: 'identity_provider'
click_button 'Save changes'
expect(page).to have_content(user.name)
expect(page).to have_content('twitter_updated')
expect(page).to have_content('654321')
end
end
describe 'remove user with identities' do
it 'removes user with twitter identity' do
visit admin_user_identities_path(user)
click_link 'Delete'
expect(page).to have_content(user.name)
expect(page).not_to have_content('twitter')
end
end
end end
...@@ -3,7 +3,6 @@ require 'spec_helper' ...@@ -3,7 +3,6 @@ require 'spec_helper'
feature 'Cycle Analytics', feature: true, js: true do feature 'Cycle Analytics', feature: true, js: true do
include WaitForAjax include WaitForAjax
let(:project) { create(:project) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:guest) { create(:user) } let(:guest) { create(:user) }
let(:project) { create(:project) } let(:project) { create(:project) }
......
require 'spec_helper'
RSpec.describe 'Dashboard Active Tab', feature: true do
before do
login_as :user
end
shared_examples 'page has active tab' do |title|
it "#{title} tab" do
expect(page).to have_selector('.nav-sidebar li.active', count: 1)
expect(find('.nav-sidebar li.active')).to have_content(title)
end
end
context 'on dashboard projects' do
before do
visit dashboard_projects_path
end
it_behaves_like 'page has active tab', 'Projects'
end
context 'on dashboard issues' do
before do
visit issues_dashboard_path
end
it_behaves_like 'page has active tab', 'Issues'
end
context 'on dashboard merge requests' do
before do
visit merge_requests_dashboard_path
end
it_behaves_like 'page has active tab', 'Merge Requests'
end
context 'on dashboard groups' do
before do
visit dashboard_groups_path
end
it_behaves_like 'page has active tab', 'Groups'
end
end
require 'spec_helper'
RSpec.describe 'Dashboard Archived Project', feature: true do
let(:user) { create :user }
let(:project) { create :project}
let(:archived_project) { create(:project, :archived) }
before do
project.team << [user, :master]
archived_project.team << [user, :master]
login_as(user)
visit dashboard_projects_path
end
it 'renders non archived projects' do
expect(page).to have_link(project.name)
expect(page).not_to have_link(archived_project.name)
end
it 'renders all projects' do
click_link 'Show archived projects'
expect(page).to have_link(project.name)
expect(page).to have_link(archived_project.name)
end
end
require 'spec_helper'
RSpec.describe 'Dashboard Group', feature: true do
before do
login_as(:user)
end
it 'creates new grpup' do
visit dashboard_groups_path
click_link 'New Group'
fill_in 'group_path', with: 'Samurai'
fill_in 'group_description', with: 'Tokugawa Shogunate'
click_button 'Create group'
expect(current_path).to eq group_path(Group.find_by(name: 'Samurai'))
expect(page).to have_content('Samurai')
expect(page).to have_content('Tokugawa Shogunate')
end
end
require 'spec_helper'
RSpec.describe 'Dashboard Help', feature: true do
before do
login_as(:user)
end
it 'renders correctly markdown' do
visit help_page_path("administration/raketasks/maintenance")
expect(page).to have_content('Gather information about GitLab and the system it runs on')
node = find('.documentation h2 a#user-content-check-gitlab-configuration')
expect(node[:href]).to eq '#check-gitlab-configuration'
expect(find(:xpath, "#{node.path}/..").text).to eq 'Check GitLab configuration'
end
end
...@@ -33,10 +33,89 @@ feature 'Setup Mattermost slash commands', feature: true do ...@@ -33,10 +33,89 @@ feature 'Setup Mattermost slash commands', feature: true do
expect(value).to eq(token) expect(value).to eq(token)
end end
describe 'mattermost service is enabled' do
it 'shows the add to mattermost button' do it 'shows the add to mattermost button' do
expect(page).to have_link 'Add to Mattermost' expect(page).to have_link('Add to Mattermost')
end end
it 'shows an explanation if user is a member of no teams' do
stub_teams(count: 0)
click_link 'Add to Mattermost'
expect(page).to have_content('You aren’t a member of any team on the Mattermost instance')
expect(page).to have_link('join a team', href: "#{Gitlab.config.mattermost.host}/select_team")
end
it 'shows an explanation if user is a member of 1 team' do
stub_teams(count: 1)
click_link 'Add to Mattermost'
expect(page).to have_content('The team where the slash commands will be used in')
expect(page).to have_content('This is the only available team.')
end
it 'shows a disabled prefilled select if user is a member of 1 team' do
teams = stub_teams(count: 1)
click_link 'Add to Mattermost'
team_name = teams.first[1]['display_name']
select_element = find('select#mattermost_team_id')
selected_option = select_element.find('option[selected]')
expect(select_element['disabled']).to be(true)
expect(selected_option).to have_content(team_name.to_s)
end
it 'has a hidden input for the prefilled value if user is a member of 1 team' do
teams = stub_teams(count: 1)
click_link 'Add to Mattermost'
expect(find('input#mattermost_team_id', visible: false).value).to eq(teams.first[0].to_s)
end
it 'shows an explanation user is a member of multiple teams' do
stub_teams(count: 2)
click_link 'Add to Mattermost'
expect(page).to have_content('Select the team where the slash commands will be used in')
expect(page).to have_content('The list shows all available teams.')
end
it 'shows a select with team options user is a member of multiple teams' do
stub_teams(count: 2)
click_link 'Add to Mattermost'
select_element = find('select#mattermost_team_id')
selected_option = select_element.find('option[selected]')
expect(select_element['disabled']).to be(false)
expect(selected_option).to have_content('Select team...')
# The 'Select team...' placeholder is item `0`.
expect(select_element.all('option').count).to eq(3)
end
def stub_teams(count: 0)
teams = create_teams(count)
allow_any_instance_of(MattermostSlashCommandsService).to receive(:list_teams) { teams }
teams
end
def create_teams(count = 0)
teams = {}
count.times do |i|
i += 1
teams[i] = { id: i, display_name: i }
end
teams
end end
describe 'mattermost service is not enabled' do describe 'mattermost service is not enabled' do
......
test:
url: <%= ENV['TEST_GITLAB_REDIS_URL'] %>
...@@ -56,7 +56,6 @@ describe Gitlab::Checks::ChangeAccess, lib: true do ...@@ -56,7 +56,6 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
it 'returns an error if the user is not allowed to do forced pushes to protected branches' do it 'returns an error if the user is not allowed to do forced pushes to protected branches' do
expect(Gitlab::Checks::ForcePush).to receive(:force_push?).and_return(true) expect(Gitlab::Checks::ForcePush).to receive(:force_push?).and_return(true)
expect(user_access).to receive(:can_do_action?).with(:force_push_code_to_protected_branches).and_return(false)
expect(subject.status).to be(false) expect(subject.status).to be(false)
expect(subject.message).to eq('You are not allowed to force push code to a protected branch on this project.') expect(subject.message).to eq('You are not allowed to force push code to a protected branch on this project.')
...@@ -88,8 +87,6 @@ describe Gitlab::Checks::ChangeAccess, lib: true do ...@@ -88,8 +87,6 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
end end
it 'returns an error if the user is not allowed to delete protected branches' do it 'returns an error if the user is not allowed to delete protected branches' do
expect(user_access).to receive(:can_do_action?).with(:remove_protected_branches).and_return(false)
expect(subject.status).to be(false) expect(subject.status).to be(false)
expect(subject.message).to eq('You are not allowed to delete protected branches from this project.') expect(subject.message).to eq('You are not allowed to delete protected branches from this project.')
end end
......
...@@ -126,5 +126,16 @@ describe Gitlab::Metrics::RackMiddleware do ...@@ -126,5 +126,16 @@ describe Gitlab::Metrics::RackMiddleware do
expect(transaction.action).to eq('Grape#GET /projects/:id/archive') expect(transaction.action).to eq('Grape#GET /projects/:id/archive')
end end
it 'does not tag a transaction if route infos are missing' do
endpoint = double(:endpoint)
allow(endpoint).to receive(:route).and_raise
env['api.endpoint'] = endpoint
middleware.tag_endpoint(transaction, env)
expect(transaction.action).to be_nil
end
end end
end end
require 'spec_helper' require 'spec_helper'
describe Gitlab::Redis do describe Gitlab::Redis do
let(:redis_config) { Rails.root.join('config', 'resque.yml').to_s } include StubENV
before(:each) { clear_raw_config } before(:each) { clear_raw_config }
after(:each) { clear_raw_config } after(:each) { clear_raw_config }
...@@ -72,6 +72,20 @@ describe Gitlab::Redis do ...@@ -72,6 +72,20 @@ describe Gitlab::Redis do
expect(url2).not_to end_with('foobar') expect(url2).not_to end_with('foobar')
end end
context 'when yml file with env variable' do
let(:redis_config) { Rails.root.join('spec/fixtures/config/redis_config_with_env.yml') }
before do
stub_env('TEST_GITLAB_REDIS_URL', 'redis://redishost:6379')
end
it 'reads redis url from env variable' do
stub_const("#{described_class}::CONFIG_FILE", redis_config)
expect(described_class.url).to eq 'redis://redishost:6379'
end
end
end end
describe '._raw_config' do describe '._raw_config' do
......
require 'spec_helper'
describe Ci::Build, models: true do
let(:project) { create(:project) }
let(:pipeline) do
create(:ci_pipeline, project: project,
sha: project.commit.id,
ref: project.default_branch,
status: 'success')
end
let(:build) { create(:ci_build, pipeline: pipeline) }
it { is_expected.to validate_presence_of :ref }
it { is_expected.to respond_to :trace_html }
describe '#first_pending' do
let!(:first) { create(:ci_build, pipeline: pipeline, status: 'pending', created_at: Date.yesterday) }
let!(:second) { create(:ci_build, pipeline: pipeline, status: 'pending') }
subject { Ci::Build.first_pending }
it { is_expected.to be_a(Ci::Build) }
it('returns with the first pending build') { is_expected.to eq(first) }
end
describe '#create_from' do
before do
build.status = 'success'
build.save
end
let(:create_from_build) { Ci::Build.create_from build }
it 'exists a pending task' do
expect(Ci::Build.pending.count(:all)).to eq 0
create_from_build
expect(Ci::Build.pending.count(:all)).to be > 0
end
end
describe '#failed_but_allowed?' do
subject { build.failed_but_allowed? }
context 'when build is not allowed to fail' do
before do
build.allow_failure = false
end
context 'and build.status is success' do
before do
build.status = 'success'
end
it { is_expected.to be_falsey }
end
context 'and build.status is failed' do
before do
build.status = 'failed'
end
it { is_expected.to be_falsey }
end
end
context 'when build is allowed to fail' do
before do
build.allow_failure = true
end
context 'and build.status is success' do
before do
build.status = 'success'
end
it { is_expected.to be_falsey }
end
context 'and build.status is failed' do
before do
build.status = 'failed'
end
it { is_expected.to be_truthy }
end
end
end
describe '#persisted_environment' do
before do
@environment = create(:environment, project: project, name: "foo-#{project.default_branch}")
end
subject { build.persisted_environment }
context 'referenced literally' do
let(:build) { create(:ci_build, pipeline: pipeline, environment: "foo-#{project.default_branch}") }
it { is_expected.to eq(@environment) }
end
context 'referenced with a variable' do
let(:build) { create(:ci_build, pipeline: pipeline, environment: "foo-$CI_BUILD_REF_NAME") }
it { is_expected.to eq(@environment) }
end
end
describe '#trace' do
it { expect(build.trace).to be_nil }
context 'when build.trace contains text' do
let(:text) { 'example output' }
before do
build.trace = text
end
it { expect(build.trace).to eq(text) }
end
context 'when build.trace hides runners token' do
let(:token) { 'my_secret_token' }
before do
build.update(trace: token)
build.project.update(runners_token: token)
end
it { expect(build.trace).not_to include(token) }
it { expect(build.raw_trace).to include(token) }
end
context 'when build.trace hides build token' do
let(:token) { 'my_secret_token' }
before do
build.update(trace: token)
build.update(token: token)
end
it { expect(build.trace).not_to include(token) }
it { expect(build.raw_trace).to include(token) }
end
end
describe '#raw_trace' do
subject { build.raw_trace }
context 'when build.trace hides runners token' do
let(:token) { 'my_secret_token' }
before do
build.project.update(runners_token: token)
build.update(trace: token)
end
it { is_expected.not_to include(token) }
end
context 'when build.trace hides build token' do
let(:token) { 'my_secret_token' }
before do
build.update(token: token)
build.update(trace: token)
end
it { is_expected.not_to include(token) }
end
end
context '#append_trace' do
subject { build.trace_html }
context 'when build.trace hides runners token' do
let(:token) { 'my_secret_token' }
before do
build.project.update(runners_token: token)
build.append_trace(token, 0)
end
it { is_expected.not_to include(token) }
end
context 'when build.trace hides build token' do
let(:token) { 'my_secret_token' }
before do
build.update(token: token)
build.append_trace(token, 0)
end
it { is_expected.not_to include(token) }
end
end
# TODO: build timeout
# describe :timeout do
# subject { build.timeout }
#
# it { is_expected.to eq(pipeline.project.timeout) }
# end
describe '#options' do
let(:options) do
{
image: "ruby:2.1",
services: [
"postgres"
]
}
end
subject { build.options }
it { is_expected.to eq(options) }
end
# TODO: allow_git_fetch
# describe :allow_git_fetch do
# subject { build.allow_git_fetch }
#
# it { is_expected.to eq(project.allow_git_fetch) }
# end
describe '#project' do
subject { build.project }
it { is_expected.to eq(pipeline.project) }
end
describe '#project_id' do
subject { build.project_id }
it { is_expected.to eq(pipeline.project_id) }
end
describe '#project_name' do
subject { build.project_name }
it { is_expected.to eq(project.name) }
end
describe '#extract_coverage' do
context 'valid content & regex' do
subject { build.extract_coverage('Coverage 1033 / 1051 LOC (98.29%) covered', '\(\d+.\d+\%\) covered') }
it { is_expected.to eq(98.29) }
end
context 'valid content & bad regex' do
subject { build.extract_coverage('Coverage 1033 / 1051 LOC (98.29%) covered', 'very covered') }
it { is_expected.to be_nil }
end
context 'no coverage content & regex' do
subject { build.extract_coverage('No coverage for today :sad:', '\(\d+.\d+\%\) covered') }
it { is_expected.to be_nil }
end
context 'multiple results in content & regex' do
subject { build.extract_coverage(' (98.39%) covered. (98.29%) covered', '\(\d+.\d+\%\) covered') }
it { is_expected.to eq(98.29) }
end
context 'using a regex capture' do
subject { build.extract_coverage('TOTAL 9926 3489 65%', 'TOTAL\s+\d+\s+\d+\s+(\d{1,3}\%)') }
it { is_expected.to eq(65) }
end
end
describe '#ref_slug' do
{
'master' => 'master',
'1-foo' => '1-foo',
'fix/1-foo' => 'fix-1-foo',
'fix-1-foo' => 'fix-1-foo',
'a' * 63 => 'a' * 63,
'a' * 64 => 'a' * 63,
'FOO' => 'foo',
}.each do |ref, slug|
it "transforms #{ref} to #{slug}" do
build.ref = ref
expect(build.ref_slug).to eq(slug)
end
end
end
describe '#variables' do
let(:container_registry_enabled) { false }
let(:predefined_variables) do
[
{ key: 'CI', value: 'true', public: true },
{ key: 'GITLAB_CI', value: 'true', public: true },
{ key: 'CI_BUILD_ID', value: build.id.to_s, public: true },
{ key: 'CI_BUILD_TOKEN', value: build.token, public: false },
{ key: 'CI_BUILD_REF', value: build.sha, public: true },
{ key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true },
{ key: 'CI_BUILD_REF_NAME', value: 'master', public: true },
{ key: 'CI_BUILD_REF_SLUG', value: 'master', public: true },
{ key: 'CI_BUILD_NAME', value: 'test', public: true },
{ key: 'CI_BUILD_STAGE', value: 'test', public: true },
{ key: 'CI_SERVER_NAME', value: 'GitLab', public: true },
{ key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true },
{ key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true },
{ key: 'CI_PROJECT_ID', value: project.id.to_s, public: true },
{ key: 'CI_PROJECT_NAME', value: project.path, public: true },
{ key: 'CI_PROJECT_PATH', value: project.path_with_namespace, public: true },
{ key: 'CI_PROJECT_NAMESPACE', value: project.namespace.path, public: true },
{ key: 'CI_PROJECT_URL', value: project.web_url, public: true },
{ key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true }
]
end
before do
stub_container_registry_config(enabled: container_registry_enabled, host_port: 'registry.example.com')
end
subject { build.variables }
context 'returns variables' do
before do
build.yaml_variables = []
end
it { is_expected.to eq(predefined_variables) }
end
context 'when build has user' do
let(:user) { create(:user, username: 'starter') }
let(:user_variables) do
[
{ key: 'GITLAB_USER_ID', value: user.id.to_s, public: true },
{ key: 'GITLAB_USER_EMAIL', value: user.email, public: true }
]
end
before do
build.update_attributes(user: user)
end
it { user_variables.each { |v| is_expected.to include(v) } }
end
context 'when build has an environment' do
before do
build.update(environment: 'production')
create(:environment, project: build.project, name: 'production', slug: 'prod-slug')
end
let(:environment_variables) do
[
{ key: 'CI_ENVIRONMENT_NAME', value: 'production', public: true },
{ key: 'CI_ENVIRONMENT_SLUG', value: 'prod-slug', public: true }
]
end
it { environment_variables.each { |v| is_expected.to include(v) } }
end
context 'when build started manually' do
before do
build.update_attributes(when: :manual)
end
let(:manual_variable) do
{ key: 'CI_BUILD_MANUAL', value: 'true', public: true }
end
it { is_expected.to include(manual_variable) }
end
context 'when build is for tag' do
let(:tag_variable) do
{ key: 'CI_BUILD_TAG', value: 'master', public: true }
end
before do
build.update_attributes(tag: true)
end
it { is_expected.to include(tag_variable) }
end
context 'when secure variable is defined' do
let(:secure_variable) do
{ key: 'SECRET_KEY', value: 'secret_value', public: false }
end
before do
build.project.variables << Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value')
end
it { is_expected.to include(secure_variable) }
end
context 'when build is for triggers' do
let(:trigger) { create(:ci_trigger, project: project) }
let(:trigger_request) { create(:ci_trigger_request_with_variables, pipeline: pipeline, trigger: trigger) }
let(:user_trigger_variable) do
{ key: :TRIGGER_KEY_1, value: 'TRIGGER_VALUE_1', public: false }
end
let(:predefined_trigger_variable) do
{ key: 'CI_BUILD_TRIGGERED', value: 'true', public: true }
end
before do
build.trigger_request = trigger_request
end
it { is_expected.to include(user_trigger_variable) }
it { is_expected.to include(predefined_trigger_variable) }
end
context 'when yaml_variables are undefined' do
before do
build.yaml_variables = nil
end
context 'use from gitlab-ci.yml' do
before do
stub_ci_pipeline_yaml_file(config)
end
context 'when config is not found' do
let(:config) { nil }
it { is_expected.to eq(predefined_variables) }
end
context 'when config does not have a questioned job' do
let(:config) do
YAML.dump({
test_other: {
script: 'Hello World'
}
})
end
it { is_expected.to eq(predefined_variables) }
end
context 'when config has variables' do
let(:config) do
YAML.dump({
test: {
script: 'Hello World',
variables: {
KEY: 'value'
}
}
})
end
let(:variables) do
[{ key: 'KEY', value: 'value', public: true }]
end
it { is_expected.to eq(predefined_variables + variables) }
end
end
end
context 'when container registry is enabled' do
let(:container_registry_enabled) { true }
let(:ci_registry) do
{ key: 'CI_REGISTRY', value: 'registry.example.com', public: true }
end
let(:ci_registry_image) do
{ key: 'CI_REGISTRY_IMAGE', value: project.container_registry_repository_url, public: true }
end
context 'and is disabled for project' do
before do
project.update(container_registry_enabled: false)
end
it { is_expected.to include(ci_registry) }
it { is_expected.not_to include(ci_registry_image) }
end
context 'and is enabled for project' do
before do
project.update(container_registry_enabled: true)
end
it { is_expected.to include(ci_registry) }
it { is_expected.to include(ci_registry_image) }
end
end
context 'when runner is assigned to build' do
let(:runner) { create(:ci_runner, description: 'description', tag_list: ['docker', 'linux']) }
before do
build.update(runner: runner)
end
it { is_expected.to include({ key: 'CI_RUNNER_ID', value: runner.id.to_s, public: true }) }
it { is_expected.to include({ key: 'CI_RUNNER_DESCRIPTION', value: 'description', public: true }) }
it { is_expected.to include({ key: 'CI_RUNNER_TAGS', value: 'docker, linux', public: true }) }
end
context 'when build is for a deployment' do
let(:deployment_variable) { { key: 'KUBERNETES_TOKEN', value: 'TOKEN', public: false } }
before do
build.environment = 'production'
allow(project).to receive(:deployment_variables).and_return([deployment_variable])
end
it { is_expected.to include(deployment_variable) }
end
context 'returns variables in valid order' do
before do
allow(build).to receive(:predefined_variables) { ['predefined'] }
allow(project).to receive(:predefined_variables) { ['project'] }
allow(pipeline).to receive(:predefined_variables) { ['pipeline'] }
allow(build).to receive(:yaml_variables) { ['yaml'] }
allow(project).to receive(:secret_variables) { ['secret'] }
end
it { is_expected.to eq(%w[predefined project pipeline yaml secret]) }
end
end
describe '#has_tags?' do
context 'when build has tags' do
subject { create(:ci_build, tag_list: ['tag']) }
it { is_expected.to have_tags }
end
context 'when build does not have tags' do
subject { create(:ci_build, tag_list: []) }
it { is_expected.not_to have_tags }
end
end
describe '#any_runners_online?' do
subject { build.any_runners_online? }
context 'when no runners' do
it { is_expected.to be_falsey }
end
context 'when there are runners' do
let(:runner) { create(:ci_runner) }
before do
build.project.runners << runner
runner.update_attributes(contacted_at: 1.second.ago)
end
it { is_expected.to be_truthy }
it 'that is inactive' do
runner.update_attributes(active: false)
is_expected.to be_falsey
end
it 'that is not online' do
runner.update_attributes(contacted_at: nil)
is_expected.to be_falsey
end
it 'that cannot handle build' do
expect_any_instance_of(Ci::Runner).to receive(:can_pick?).and_return(false)
is_expected.to be_falsey
end
end
end
describe '#stuck?' do
subject { build.stuck? }
context "when commit_status.status is pending" do
before do
build.status = 'pending'
end
it { is_expected.to be_truthy }
context "and there are specific runner" do
let(:runner) { create(:ci_runner, contacted_at: 1.second.ago) }
before do
build.project.runners << runner
runner.save
end
it { is_expected.to be_falsey }
end
end
%w[success failed canceled running].each do |state|
context "when commit_status.status is #{state}" do
before do
build.status = state
end
it { is_expected.to be_falsey }
end
end
end
describe '#artifacts?' do
subject { build.artifacts? }
context 'artifacts archive does not exist' do
before do
build.update_attributes(artifacts_file: nil)
end
it { is_expected.to be_falsy }
end
context 'artifacts archive exists' do
let(:build) { create(:ci_build, :artifacts) }
it { is_expected.to be_truthy }
context 'is expired' do
before { build.update(artifacts_expire_at: Time.now - 7.days) }
it { is_expected.to be_falsy }
end
context 'is not expired' do
before { build.update(artifacts_expire_at: Time.now + 7.days) }
it { is_expected.to be_truthy }
end
end
end
describe '#artifacts_expired?' do
subject { build.artifacts_expired? }
context 'is expired' do
before { build.update(artifacts_expire_at: Time.now - 7.days) }
it { is_expected.to be_truthy }
end
context 'is not expired' do
before { build.update(artifacts_expire_at: Time.now + 7.days) }
it { is_expected.to be_falsey }
end
end
describe '#artifacts_metadata?' do
subject { build.artifacts_metadata? }
context 'artifacts metadata does not exist' do
it { is_expected.to be_falsy }
end
context 'artifacts archive is a zip file and metadata exists' do
let(:build) { create(:ci_build, :artifacts) }
it { is_expected.to be_truthy }
end
end
describe '#repo_url' do
let(:build) { create(:ci_build) }
let(:project) { build.project }
subject { build.repo_url }
it { is_expected.to be_a(String) }
it { is_expected.to end_with(".git") }
it { is_expected.to start_with(project.web_url[0..6]) }
it { is_expected.to include(build.token) }
it { is_expected.to include('gitlab-ci-token') }
it { is_expected.to include(project.web_url[7..-1]) }
end
describe '#artifacts_expire_in' do
subject { build.artifacts_expire_in }
it { is_expected.to be_nil }
context 'when artifacts_expire_at is specified' do
let(:expire_at) { Time.now + 7.days }
before { build.artifacts_expire_at = expire_at }
it { is_expected.to be_within(5).of(expire_at - Time.now) }
end
end
describe '#artifacts_expire_in=' do
subject { build.artifacts_expire_in }
it 'when assigning valid duration' do
build.artifacts_expire_in = '7 days'
is_expected.to be_within(10).of(7.days.to_i)
end
it 'when assigning invalid duration' do
expect { build.artifacts_expire_in = '7 elephants' }.to raise_error(ChronicDuration::DurationParseError)
is_expected.to be_nil
end
it 'when resseting value' do
build.artifacts_expire_in = nil
is_expected.to be_nil
end
end
describe '#keep_artifacts!' do
let(:build) { create(:ci_build, artifacts_expire_at: Time.now + 7.days) }
it 'to reset expire_at' do
build.keep_artifacts!
expect(build.artifacts_expire_at).to be_nil
end
end
describe '#depends_on_builds' do
let!(:build) { create(:ci_build, pipeline: pipeline, name: 'build', stage_idx: 0, stage: 'build') }
let!(:rspec_test) { create(:ci_build, pipeline: pipeline, name: 'rspec', stage_idx: 1, stage: 'test') }
let!(:rubocop_test) { create(:ci_build, pipeline: pipeline, name: 'rubocop', stage_idx: 1, stage: 'test') }
let!(:staging) { create(:ci_build, pipeline: pipeline, name: 'staging', stage_idx: 2, stage: 'deploy') }
it 'expects to have no dependents if this is first build' do
expect(build.depends_on_builds).to be_empty
end
it 'expects to have one dependent if this is test' do
expect(rspec_test.depends_on_builds.map(&:id)).to contain_exactly(build.id)
end
it 'expects to have all builds from build and test stage if this is last' do
expect(staging.depends_on_builds.map(&:id)).to contain_exactly(build.id, rspec_test.id, rubocop_test.id)
end
it 'expects to have retried builds instead the original ones' do
retried_rspec = Ci::Build.retry(rspec_test)
expect(staging.depends_on_builds.map(&:id)).to contain_exactly(build.id, retried_rspec.id, rubocop_test.id)
end
end
def create_mr(build, pipeline, factory: :merge_request, created_at: Time.now)
create(factory, source_project_id: pipeline.gl_project_id,
target_project_id: pipeline.gl_project_id,
source_branch: build.ref,
created_at: created_at)
end
describe '#merge_request' do
context 'when a MR has a reference to the pipeline' do
before do
@merge_request = create_mr(build, pipeline, factory: :merge_request)
commits = [double(id: pipeline.sha)]
allow(@merge_request).to receive(:commits).and_return(commits)
allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request])
end
it 'returns the single associated MR' do
expect(build.merge_request.id).to eq(@merge_request.id)
end
end
context 'when there is not a MR referencing the pipeline' do
it 'returns nil' do
expect(build.merge_request).to be_nil
end
end
context 'when more than one MR have a reference to the pipeline' do
before do
@merge_request = create_mr(build, pipeline, factory: :merge_request)
@merge_request.close!
@merge_request2 = create_mr(build, pipeline, factory: :merge_request)
commits = [double(id: pipeline.sha)]
allow(@merge_request).to receive(:commits).and_return(commits)
allow(@merge_request2).to receive(:commits).and_return(commits)
allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request, @merge_request2])
end
it 'returns the first MR' do
expect(build.merge_request.id).to eq(@merge_request.id)
end
end
context 'when a Build is created after the MR' do
before do
@merge_request = create_mr(build, pipeline, factory: :merge_request_with_diffs)
pipeline2 = create(:ci_pipeline, project: project)
@build2 = create(:ci_build, pipeline: pipeline2)
allow(@merge_request).to receive(:commits_sha).
and_return([pipeline.sha, pipeline2.sha])
allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request])
end
it 'returns the current MR' do
expect(@build2.merge_request.id).to eq(@merge_request.id)
end
end
end
describe 'build erasable' do
shared_examples 'erasable' do
it 'removes artifact file' do
expect(build.artifacts_file.exists?).to be_falsy
end
it 'removes artifact metadata file' do
expect(build.artifacts_metadata.exists?).to be_falsy
end
it 'erases build trace in trace file' do
expect(build.trace).to be_empty
end
it 'sets erased to true' do
expect(build.erased?).to be true
end
it 'sets erase date' do
expect(build.erased_at).not_to be_falsy
end
end
context 'build is not erasable' do
let!(:build) { create(:ci_build) }
describe '#erase' do
subject { build.erase }
it { is_expected.to be false }
end
describe '#erasable?' do
subject { build.erasable? }
it { is_expected.to eq false }
end
end
context 'build is erasable' do
let!(:build) { create(:ci_build, :trace, :success, :artifacts) }
describe '#erase' do
before do
build.erase(erased_by: user)
end
context 'erased by user' do
let!(:user) { create(:user, username: 'eraser') }
include_examples 'erasable'
it 'records user who erased a build' do
expect(build.erased_by).to eq user
end
end
context 'erased by system' do
let(:user) { nil }
include_examples 'erasable'
it 'does not set user who erased a build' do
expect(build.erased_by).to be_nil
end
end
end
describe '#erasable?' do
subject { build.erasable? }
it { is_expected.to be_truthy }
end
describe '#erased?' do
let!(:build) { create(:ci_build, :trace, :success, :artifacts) }
subject { build.erased? }
context 'build has not been erased' do
it { is_expected.to be_falsey }
end
context 'build has been erased' do
before do
build.erase
end
it { is_expected.to be_truthy }
end
end
context 'metadata and build trace are not available' do
let!(:build) { create(:ci_build, :success, :artifacts) }
before do
build.remove_artifacts_metadata!
end
describe '#erase' do
it 'does not raise error' do
expect { build.erase }.not_to raise_error
end
end
end
end
end
describe '#commit' do
it 'returns commit pipeline has been created for' do
expect(build.commit).to eq project.commit
end
end
describe '#when' do
subject { build.when }
context 'when `when` is undefined' do
before do
build.when = nil
end
context 'use from gitlab-ci.yml' do
before do
stub_ci_pipeline_yaml_file(config)
end
context 'when config is not found' do
let(:config) { nil }
it { is_expected.to eq('on_success') }
end
context 'when config does not have a questioned job' do
let(:config) do
YAML.dump({
test_other: {
script: 'Hello World'
}
})
end
it { is_expected.to eq('on_success') }
end
context 'when config has `when`' do
let(:config) do
YAML.dump({
test: {
script: 'Hello World',
when: 'always'
}
})
end
it { is_expected.to eq('always') }
end
end
end
end
describe '#cancelable?' do
subject { build }
context 'when build is cancelable' do
context 'when build is pending' do
it { is_expected.to be_cancelable }
end
context 'when build is running' do
before do
build.run!
end
it { is_expected.to be_cancelable }
end
end
context 'when build is not cancelable' do
context 'when build is successful' do
before do
build.success!
end
it { is_expected.not_to be_cancelable }
end
context 'when build is failed' do
before do
build.drop!
end
it { is_expected.not_to be_cancelable }
end
end
end
describe '#retryable?' do
subject { build }
context 'when build is retryable' do
context 'when build is successful' do
before do
build.success!
end
it { is_expected.to be_retryable }
end
context 'when build is failed' do
before do
build.drop!
end
it { is_expected.to be_retryable }
end
context 'when build is canceled' do
before do
build.cancel!
end
it { is_expected.to be_retryable }
end
end
context 'when build is not retryable' do
context 'when build is running' do
before do
build.run!
end
it { is_expected.not_to be_retryable }
end
context 'when build is skipped' do
before do
build.skip!
end
it { is_expected.not_to be_retryable }
end
end
end
describe '#manual?' do
before do
build.update(when: value)
end
subject { build.manual? }
context 'when is set to manual' do
let(:value) { 'manual' }
it { is_expected.to be_truthy }
end
context 'when set to something else' do
let(:value) { 'something else' }
it { is_expected.to be_falsey }
end
end
describe '#other_actions' do
let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
let!(:other_build) { create(:ci_build, :manual, pipeline: pipeline, name: 'other action') }
subject { build.other_actions }
it 'returns other actions' do
is_expected.to contain_exactly(other_build)
end
context 'when build is retried' do
let!(:new_build) { Ci::Build.retry(build) }
it 'does not return any of them' do
is_expected.not_to include(build, new_build)
end
end
context 'when other build is retried' do
let!(:retried_build) { Ci::Build.retry(other_build) }
it 'returns a retried build' do
is_expected.to contain_exactly(retried_build)
end
end
end
describe '#play' do
let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
subject { build.play }
it 'enqueues a build' do
is_expected.to be_pending
is_expected.to eq(build)
end
context 'for successful build' do
before do
build.update(status: 'success')
end
it 'creates a new build' do
is_expected.to be_pending
is_expected.not_to eq(build)
end
end
end
describe '#when' do
subject { build.when }
context 'when `when` is undefined' do
before do
build.when = nil
end
context 'use from gitlab-ci.yml' do
before do
stub_ci_pipeline_yaml_file(config)
end
context 'when config is not found' do
let(:config) { nil }
it { is_expected.to eq('on_success') }
end
context 'when config does not have a questioned job' do
let(:config) do
YAML.dump({
test_other: {
script: 'Hello World'
}
})
end
it { is_expected.to eq('on_success') }
end
context 'when config has when' do
let(:config) do
YAML.dump({
test: {
script: 'Hello World',
when: 'always'
}
})
end
it { is_expected.to eq('always') }
end
end
end
end
describe '#retryable?' do
context 'when build is running' do
before { build.run! }
it 'returns false' do
expect(build).not_to be_retryable
end
end
context 'when build is finished' do
before do
build.success!
end
it 'returns true' do
expect(build).to be_retryable
end
end
end
describe '#has_environment?' do
subject { build.has_environment? }
context 'when environment is defined' do
before do
build.update(environment: 'review')
end
it { is_expected.to be_truthy }
end
context 'when environment is not defined' do
before do
build.update(environment: nil)
end
it { is_expected.to be_falsey }
end
end
describe '#starts_environment?' do
subject { build.starts_environment? }
context 'when environment is defined' do
before do
build.update(environment: 'review')
end
context 'no action is defined' do
it { is_expected.to be_truthy }
end
context 'and start action is defined' do
before do
build.update(options: { environment: { action: 'start' } } )
end
it { is_expected.to be_truthy }
end
end
context 'when environment is not defined' do
before do
build.update(environment: nil)
end
it { is_expected.to be_falsey }
end
end
describe '#stops_environment?' do
subject { build.stops_environment? }
context 'when environment is defined' do
before do
build.update(environment: 'review')
end
context 'no action is defined' do
it { is_expected.to be_falsey }
end
context 'and stop action is defined' do
before do
build.update(options: { environment: { action: 'stop' } } )
end
it { is_expected.to be_truthy }
end
end
context 'when environment is not defined' do
before do
build.update(environment: nil)
end
it { is_expected.to be_falsey }
end
end
describe '#last_deployment' do
subject { build.last_deployment }
context 'when multiple deployments are created' do
let!(:deployment1) { create(:deployment, deployable: build) }
let!(:deployment2) { create(:deployment, deployable: build) }
it 'returns the latest one' do
is_expected.to eq(deployment2)
end
end
end
describe '#outdated_deployment?' do
subject { build.outdated_deployment? }
context 'when build succeeded' do
let(:build) { create(:ci_build, :success) }
let!(:deployment) { create(:deployment, deployable: build) }
context 'current deployment is latest' do
it { is_expected.to be_falsey }
end
context 'current deployment is not latest on environment' do
let!(:deployment2) { create(:deployment, environment: deployment.environment) }
it { is_expected.to be_truthy }
end
end
context 'when build failed' do
let(:build) { create(:ci_build, :failed) }
it { is_expected.to be_falsey }
end
end
describe '#expanded_environment_name' do
subject { build.expanded_environment_name }
context 'when environment uses $CI_BUILD_REF_NAME' do
let(:build) do
create(:ci_build,
ref: 'master',
environment: 'review/$CI_BUILD_REF_NAME')
end
it { is_expected.to eq('review/master') }
end
context 'when environment uses yaml_variables containing symbol keys' do
let(:build) do
create(:ci_build,
yaml_variables: [{ key: :APP_HOST, value: 'host' }],
environment: 'review/$APP_HOST')
end
it { is_expected.to eq('review/host') }
end
end
describe '#detailed_status' do
let(:user) { create(:user) }
it 'returns a detailed status' do
expect(build.detailed_status(user))
.to be_a Gitlab::Ci::Status::Build::Cancelable
end
end
end
require 'spec_helper' require 'spec_helper'
describe Ci::Build, models: true do describe Ci::Build, :models do
let(:project) { create(:project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
let(:test_trace) { 'This is a test' }
let(:pipeline) do
create(:ci_pipeline, project: project,
sha: project.commit.id,
ref: project.default_branch,
status: 'success')
end
it { is_expected.to belong_to(:runner) }
it { is_expected.to belong_to(:trigger_request) }
it { is_expected.to belong_to(:erased_by) }
it { is_expected.to have_many(:deployments) }
it { is_expected.to validate_presence_of :ref }
it { is_expected.to respond_to :trace_html }
describe '#any_runners_online?' do
subject { build.any_runners_online? }
context 'when no runners' do
it { is_expected.to be_falsey }
end
context 'when there are runners' do
let(:runner) { create(:ci_runner) }
before do
build.project.runners << runner
runner.update_attributes(contacted_at: 1.second.ago)
end
it { is_expected.to be_truthy }
it 'that is inactive' do
runner.update_attributes(active: false)
is_expected.to be_falsey
end
it 'that is not online' do
runner.update_attributes(contacted_at: nil)
is_expected.to be_falsey
end
it 'that cannot handle build' do
expect_any_instance_of(Ci::Runner).to receive(:can_pick?).and_return(false)
is_expected.to be_falsey
end
end
end
describe '#append_trace' do
subject { build.trace_html }
context 'when build.trace hides runners token' do
let(:token) { 'my_secret_token' }
before do
build.project.update(runners_token: token)
build.append_trace(token, 0)
end
it { is_expected.not_to include(token) }
end
context 'when build.trace hides build token' do
let(:token) { 'my_secret_token' }
before do
build.update(token: token)
build.append_trace(token, 0)
end
it { is_expected.not_to include(token) }
end
end
describe '#artifacts?' do
subject { build.artifacts? }
context 'artifacts archive does not exist' do
before do
build.update_attributes(artifacts_file: nil)
end
it { is_expected.to be_falsy }
end
context 'artifacts archive exists' do
let(:build) { create(:ci_build, :artifacts) }
it { is_expected.to be_truthy }
context 'is expired' do
before { build.update(artifacts_expire_at: Time.now - 7.days) }
it { is_expected.to be_falsy }
end
context 'is not expired' do
before { build.update(artifacts_expire_at: Time.now + 7.days) }
it { is_expected.to be_truthy }
end
end
end
describe '#artifacts_expired?' do
subject { build.artifacts_expired? }
context 'is expired' do
before { build.update(artifacts_expire_at: Time.now - 7.days) }
it { is_expected.to be_truthy }
end
context 'is not expired' do
before { build.update(artifacts_expire_at: Time.now + 7.days) }
it { is_expected.to be_falsey }
end
end
describe '#artifacts_metadata?' do
subject { build.artifacts_metadata? }
context 'artifacts metadata does not exist' do
it { is_expected.to be_falsy }
end
context 'artifacts archive is a zip file and metadata exists' do
let(:build) { create(:ci_build, :artifacts) }
it { is_expected.to be_truthy }
end
end
describe '#artifacts_expire_in' do
subject { build.artifacts_expire_in }
it { is_expected.to be_nil }
context 'when artifacts_expire_at is specified' do
let(:expire_at) { Time.now + 7.days }
before { build.artifacts_expire_at = expire_at }
it { is_expected.to be_within(5).of(expire_at - Time.now) }
end
end
describe '#artifacts_expire_in=' do
subject { build.artifacts_expire_in }
it 'when assigning valid duration' do
build.artifacts_expire_in = '7 days'
is_expected.to be_within(10).of(7.days.to_i)
end
it 'when assigning invalid duration' do
expect { build.artifacts_expire_in = '7 elephants' }.to raise_error(ChronicDuration::DurationParseError)
is_expected.to be_nil
end
it 'when resseting value' do
build.artifacts_expire_in = nil
is_expected.to be_nil
end
end
describe '#commit' do
it 'returns commit pipeline has been created for' do
expect(build.commit).to eq project.commit
end
end
describe '#create_from' do
before do
build.status = 'success'
build.save
end
let(:create_from_build) { Ci::Build.create_from build }
it 'exists a pending task' do
expect(Ci::Build.pending.count(:all)).to eq 0
create_from_build
expect(Ci::Build.pending.count(:all)).to be > 0
end
end
describe '#depends_on_builds' do
let!(:build) { create(:ci_build, pipeline: pipeline, name: 'build', stage_idx: 0, stage: 'build') }
let!(:rspec_test) { create(:ci_build, pipeline: pipeline, name: 'rspec', stage_idx: 1, stage: 'test') }
let!(:rubocop_test) { create(:ci_build, pipeline: pipeline, name: 'rubocop', stage_idx: 1, stage: 'test') }
let!(:staging) { create(:ci_build, pipeline: pipeline, name: 'staging', stage_idx: 2, stage: 'deploy') }
it 'expects to have no dependents if this is first build' do
expect(build.depends_on_builds).to be_empty
end
it 'expects to have one dependent if this is test' do
expect(rspec_test.depends_on_builds.map(&:id)).to contain_exactly(build.id)
end
it 'expects to have all builds from build and test stage if this is last' do
expect(staging.depends_on_builds.map(&:id)).to contain_exactly(build.id, rspec_test.id, rubocop_test.id)
end
it 'expects to have retried builds instead the original ones' do
retried_rspec = Ci::Build.retry(rspec_test)
expect(staging.depends_on_builds.map(&:id)).to contain_exactly(build.id, retried_rspec.id, rubocop_test.id)
end
end
describe '#detailed_status' do
let(:user) { create(:user) }
it 'returns a detailed status' do
expect(build.detailed_status(user))
.to be_a Gitlab::Ci::Status::Build::Cancelable
end
end
describe 'deployment' do
describe '#last_deployment' do
subject { build.last_deployment }
context 'when multiple deployments are created' do
let!(:deployment1) { create(:deployment, deployable: build) }
let!(:deployment2) { create(:deployment, deployable: build) }
it 'returns the latest one' do
is_expected.to eq(deployment2)
end
end
end
describe '#outdated_deployment?' do
subject { build.outdated_deployment? }
context 'when build succeeded' do
let(:build) { create(:ci_build, :success) }
let!(:deployment) { create(:deployment, deployable: build) }
context 'current deployment is latest' do
it { is_expected.to be_falsey }
end
context 'current deployment is not latest on environment' do
let!(:deployment2) { create(:deployment, environment: deployment.environment) }
it { is_expected.to be_truthy }
end
end
context 'when build failed' do
let(:build) { create(:ci_build, :failed) }
it { is_expected.to be_falsey }
end
end
end
describe 'environment' do
describe '#has_environment?' do
subject { build.has_environment? }
context 'when environment is defined' do
before do
build.update(environment: 'review')
end
it { is_expected.to be_truthy }
end
context 'when environment is not defined' do
before do
build.update(environment: nil)
end
it { is_expected.to be_falsey }
end
end
describe '#expanded_environment_name' do
subject { build.expanded_environment_name }
context 'when environment uses $CI_BUILD_REF_NAME' do
let(:build) do
create(:ci_build,
ref: 'master',
environment: 'review/$CI_BUILD_REF_NAME')
end
it { is_expected.to eq('review/master') }
end
context 'when environment uses yaml_variables containing symbol keys' do
let(:build) do
create(:ci_build,
yaml_variables: [{ key: :APP_HOST, value: 'host' }],
environment: 'review/$APP_HOST')
end
it { is_expected.to eq('review/host') }
end
end
describe '#starts_environment?' do
subject { build.starts_environment? }
context 'when environment is defined' do
before do
build.update(environment: 'review')
end
context 'no action is defined' do
it { is_expected.to be_truthy }
end
context 'and start action is defined' do
before do
build.update(options: { environment: { action: 'start' } } )
end
it { is_expected.to be_truthy }
end
end
context 'when environment is not defined' do
before do
build.update(environment: nil)
end
it { is_expected.to be_falsey }
end
end
describe '#stops_environment?' do
subject { build.stops_environment? }
context 'when environment is defined' do
before do
build.update(environment: 'review')
end
context 'no action is defined' do
it { is_expected.to be_falsey }
end
context 'and stop action is defined' do
before do
build.update(options: { environment: { action: 'stop' } } )
end
it { is_expected.to be_truthy }
end
end
context 'when environment is not defined' do
before do
build.update(environment: nil)
end
it { is_expected.to be_falsey }
end
end
end
describe 'erasable build' do
shared_examples 'erasable' do
it 'removes artifact file' do
expect(build.artifacts_file.exists?).to be_falsy
end
it 'removes artifact metadata file' do
expect(build.artifacts_metadata.exists?).to be_falsy
end
it 'erases build trace in trace file' do
expect(build.trace).to be_empty
end
it 'sets erased to true' do
expect(build.erased?).to be true
end
it 'sets erase date' do
expect(build.erased_at).not_to be_falsy
end
end
context 'build is not erasable' do
let!(:build) { create(:ci_build) }
describe '#erase' do
subject { build.erase }
it { is_expected.to be false }
end
describe '#erasable?' do
subject { build.erasable? }
it { is_expected.to eq false }
end
end
context 'build is erasable' do
let!(:build) { create(:ci_build, :trace, :success, :artifacts) }
describe '#erase' do
before do
build.erase(erased_by: user)
end
context 'erased by user' do
let!(:user) { create(:user, username: 'eraser') }
include_examples 'erasable'
it 'records user who erased a build' do
expect(build.erased_by).to eq user
end
end
context 'erased by system' do
let(:user) { nil }
include_examples 'erasable'
it 'does not set user who erased a build' do
expect(build.erased_by).to be_nil
end
end
end
describe '#erasable?' do
subject { build.erasable? }
it { is_expected.to be_truthy }
end
describe '#erased?' do
let!(:build) { create(:ci_build, :trace, :success, :artifacts) }
subject { build.erased? }
context 'build has not been erased' do
it { is_expected.to be_falsey }
end
context 'build has been erased' do
before do
build.erase
end
it { is_expected.to be_truthy }
end
end
context 'metadata and build trace are not available' do
let!(:build) { create(:ci_build, :success, :artifacts) }
before do
build.remove_artifacts_metadata!
end
describe '#erase' do
it 'does not raise error' do
expect { build.erase }.not_to raise_error
end
end
end
end
end
describe '#extract_coverage' do
context 'valid content & regex' do
subject { build.extract_coverage('Coverage 1033 / 1051 LOC (98.29%) covered', '\(\d+.\d+\%\) covered') }
it { is_expected.to eq(98.29) }
end
context 'valid content & bad regex' do
subject { build.extract_coverage('Coverage 1033 / 1051 LOC (98.29%) covered', 'very covered') }
it { is_expected.to be_nil }
end
context 'no coverage content & regex' do
subject { build.extract_coverage('No coverage for today :sad:', '\(\d+.\d+\%\) covered') }
it { is_expected.to be_nil }
end
context 'multiple results in content & regex' do
subject { build.extract_coverage(' (98.39%) covered. (98.29%) covered', '\(\d+.\d+\%\) covered') }
it { is_expected.to eq(98.29) }
end
context 'using a regex capture' do
subject { build.extract_coverage('TOTAL 9926 3489 65%', 'TOTAL\s+\d+\s+\d+\s+(\d{1,3}\%)') }
it { is_expected.to eq(65) }
end
end
describe '#first_pending' do
let!(:first) { create(:ci_build, pipeline: pipeline, status: 'pending', created_at: Date.yesterday) }
let!(:second) { create(:ci_build, pipeline: pipeline, status: 'pending') }
subject { Ci::Build.first_pending }
it { is_expected.to be_a(Ci::Build) }
it('returns with the first pending build') { is_expected.to eq(first) }
end
describe '#failed_but_allowed?' do
subject { build.failed_but_allowed? }
context 'when build is not allowed to fail' do
before do
build.allow_failure = false
end
context 'and build.status is success' do
before do
build.status = 'success'
end
it { is_expected.to be_falsey }
end
context 'and build.status is failed' do
before do
build.status = 'failed'
end
it { is_expected.to be_falsey }
end
end
context 'when build is allowed to fail' do
before do
build.allow_failure = true
end
context 'and build.status is success' do
before do
build.status = 'success'
end
it { is_expected.to be_falsey }
end
context 'and build.status is failed' do
before do
build.status = 'failed'
end
it { is_expected.to be_truthy }
end
end
end
describe 'flags' do
describe '#cancelable?' do
subject { build }
context 'when build is cancelable' do
context 'when build is pending' do
it { is_expected.to be_cancelable }
end
context 'when build is running' do
before do
build.run!
end
it { is_expected.to be_cancelable }
end
end
context 'when build is not cancelable' do
context 'when build is successful' do
before do
build.success!
end
it { is_expected.not_to be_cancelable }
end
context 'when build is failed' do
before do
build.drop!
end
it { is_expected.not_to be_cancelable }
end
end
end
describe '#retryable?' do
subject { build }
context 'when build is retryable' do
context 'when build is successful' do
before do
build.success!
end
it { is_expected.to be_retryable }
end
context 'when build is failed' do
before do
build.drop!
end
it { is_expected.to be_retryable }
end
context 'when build is canceled' do
before do
build.cancel!
end
it { is_expected.to be_retryable }
end
end
context 'when build is not retryable' do
context 'when build is running' do
before do
build.run!
end
it { is_expected.not_to be_retryable }
end
context 'when build is skipped' do
before do
build.skip!
end
it { is_expected.not_to be_retryable }
end
end
end
describe '#manual?' do
before do
build.update(when: value)
end
subject { build.manual? }
context 'when is set to manual' do
let(:value) { 'manual' }
it { is_expected.to be_truthy }
end
context 'when set to something else' do
let(:value) { 'something else' }
it { is_expected.to be_falsey }
end
end
end
describe '#has_tags?' do
context 'when build has tags' do
subject { create(:ci_build, tag_list: ['tag']) }
it { is_expected.to have_tags }
end
context 'when build does not have tags' do
subject { create(:ci_build, tag_list: []) }
it { is_expected.not_to have_tags }
end
end
describe '#keep_artifacts!' do
let(:build) { create(:ci_build, artifacts_expire_at: Time.now + 7.days) }
it 'to reset expire_at' do
build.keep_artifacts!
expect(build.artifacts_expire_at).to be_nil
end
end
describe '#merge_request' do
def create_mr(build, pipeline, factory: :merge_request, created_at: Time.now)
create(factory, source_project_id: pipeline.gl_project_id,
target_project_id: pipeline.gl_project_id,
source_branch: build.ref,
created_at: created_at)
end
context 'when a MR has a reference to the pipeline' do
before do
@merge_request = create_mr(build, pipeline, factory: :merge_request)
commits = [double(id: pipeline.sha)]
allow(@merge_request).to receive(:commits).and_return(commits)
allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request])
end
it 'returns the single associated MR' do
expect(build.merge_request.id).to eq(@merge_request.id)
end
end
context 'when there is not a MR referencing the pipeline' do
it 'returns nil' do
expect(build.merge_request).to be_nil
end
end
context 'when more than one MR have a reference to the pipeline' do
before do
@merge_request = create_mr(build, pipeline, factory: :merge_request)
@merge_request.close!
@merge_request2 = create_mr(build, pipeline, factory: :merge_request)
commits = [double(id: pipeline.sha)]
allow(@merge_request).to receive(:commits).and_return(commits)
allow(@merge_request2).to receive(:commits).and_return(commits)
allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request, @merge_request2])
end
it 'returns the first MR' do
expect(build.merge_request.id).to eq(@merge_request.id)
end
end
context 'when a Build is created after the MR' do
before do
@merge_request = create_mr(build, pipeline, factory: :merge_request_with_diffs)
pipeline2 = create(:ci_pipeline, project: project)
@build2 = create(:ci_build, pipeline: pipeline2)
allow(@merge_request).to receive(:commits_sha).
and_return([pipeline.sha, pipeline2.sha])
allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request])
end
it 'returns the current MR' do
expect(@build2.merge_request.id).to eq(@merge_request.id)
end
end
end
describe '#options' do
let(:options) do
{
image: "ruby:2.1",
services: [
"postgres"
]
}
end
it 'contains options' do
expect(build.options).to eq(options)
end
end
describe '#other_actions' do
let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
let!(:other_build) { create(:ci_build, :manual, pipeline: pipeline, name: 'other action') }
subject { build.other_actions }
it 'returns other actions' do
is_expected.to contain_exactly(other_build)
end
context 'when build is retried' do
let!(:new_build) { Ci::Build.retry(build) }
it 'does not return any of them' do
is_expected.not_to include(build, new_build)
end
end
context 'when other build is retried' do
let!(:retried_build) { Ci::Build.retry(other_build) }
it 'returns a retried build' do
is_expected.to contain_exactly(retried_build)
end
end
end
describe '#persisted_environment' do
before do
@environment = create(:environment, project: project, name: "foo-#{project.default_branch}")
end
subject { build.persisted_environment }
context 'referenced literally' do
let(:build) { create(:ci_build, pipeline: pipeline, environment: "foo-#{project.default_branch}") }
it { is_expected.to eq(@environment) }
end
context 'referenced with a variable' do
let(:build) { create(:ci_build, pipeline: pipeline, environment: "foo-$CI_BUILD_REF_NAME") }
it { is_expected.to eq(@environment) }
end
end
describe '#play' do
let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
subject { build.play }
it 'enqueues a build' do
is_expected.to be_pending
is_expected.to eq(build)
end
context 'for successful build' do
before do
build.update(status: 'success')
end
it 'creates a new build' do
is_expected.to be_pending
is_expected.not_to eq(build)
end
end
end
describe 'project settings' do
describe '#timeout' do
it 'returns project timeout configuration' do
expect(build.timeout).to eq(project.build_timeout)
end
end
describe '#allow_git_fetch' do
it 'return project allow_git_fetch configuration' do
expect(build.allow_git_fetch).to eq(project.build_allow_git_fetch)
end
end
end
describe '#project' do
subject { build.project }
it { is_expected.to eq(pipeline.project) }
end
describe '#project_id' do
subject { build.project_id }
it { is_expected.to eq(pipeline.project_id) }
end
describe '#project_name' do
subject { build.project_name }
it { is_expected.to eq(project.name) }
end
describe '#raw_trace' do
subject { build.raw_trace }
context 'when build.trace hides runners token' do
let(:token) { 'my_secret_token' }
before do
build.project.update(runners_token: token)
build.update(trace: token)
end
it { is_expected.not_to include(token) }
end
context 'when build.trace hides build token' do
let(:token) { 'my_secret_token' }
before do
build.update(token: token)
build.update(trace: token)
end
it { is_expected.not_to include(token) }
end
end
describe '#ref_slug' do
{
'master' => 'master',
'1-foo' => '1-foo',
'fix/1-foo' => 'fix-1-foo',
'fix-1-foo' => 'fix-1-foo',
'a' * 63 => 'a' * 63,
'a' * 64 => 'a' * 63,
'FOO' => 'foo',
}.each do |ref, slug|
it "transforms #{ref} to #{slug}" do
build.ref = ref
expect(build.ref_slug).to eq(slug)
end
end
end
describe '#repo_url' do
let(:build) { create(:ci_build) } let(:build) { create(:ci_build) }
let(:test_trace) { 'This is a test' } let(:project) { build.project }
it { is_expected.to belong_to(:runner) } subject { build.repo_url }
it { is_expected.to belong_to(:trigger_request) }
it { is_expected.to belong_to(:erased_by) }
it { is_expected.to have_many(:deployments) } it { is_expected.to be_a(String) }
it { is_expected.to end_with(".git") }
it { is_expected.to start_with(project.web_url[0..6]) }
it { is_expected.to include(build.token) }
it { is_expected.to include('gitlab-ci-token') }
it { is_expected.to include(project.web_url[7..-1]) }
end
describe '#stuck?' do
subject { build.stuck? }
context "when commit_status.status is pending" do
before do
build.status = 'pending'
end
it { is_expected.to be_truthy }
context "and there are specific runner" do
let(:runner) { create(:ci_runner, contacted_at: 1.second.ago) }
before do
build.project.runners << runner
runner.save
end
it { is_expected.to be_falsey }
end
end
%w[success failed canceled running].each do |state|
context "when commit_status.status is #{state}" do
before do
build.status = state
end
it { is_expected.to be_falsey }
end
end
end
describe '#trace' do describe '#trace' do
it 'obfuscates project runners token' do it 'obfuscates project runners token' do
...@@ -24,6 +972,45 @@ describe Ci::Build, models: true do ...@@ -24,6 +972,45 @@ describe Ci::Build, models: true do
expect(build.trace).to eq(test_trace) expect(build.trace).to eq(test_trace)
end end
context 'when build does not have trace' do
it 'is is empty' do
expect(build.trace).to be_nil
end
end
context 'when trace contains text' do
let(:text) { 'example output' }
before do
build.trace = text
end
it { expect(build.trace).to eq(text) }
end
context 'when trace hides runners token' do
let(:token) { 'my_secret_token' }
before do
build.update(trace: token)
build.project.update(runners_token: token)
end
it { expect(build.trace).not_to include(token) }
it { expect(build.raw_trace).to include(token) }
end
context 'when build.trace hides build token' do
let(:token) { 'my_secret_token' }
before do
build.update(trace: token)
build.update(token: token)
end
it { expect(build.trace).not_to include(token) }
it { expect(build.raw_trace).to include(token) }
end
end end
describe '#has_trace_file?' do describe '#has_trace_file?' do
...@@ -111,4 +1098,289 @@ describe Ci::Build, models: true do ...@@ -111,4 +1098,289 @@ describe Ci::Build, models: true do
build.destroy build.destroy
end end
end end
describe '#when' do
subject { build.when }
context 'when `when` is undefined' do
before do
build.when = nil
end
context 'use from gitlab-ci.yml' do
before do
stub_ci_pipeline_yaml_file(config)
end
context 'when config is not found' do
let(:config) { nil }
it { is_expected.to eq('on_success') }
end
context 'when config does not have a questioned job' do
let(:config) do
YAML.dump({
test_other: {
script: 'Hello World'
}
})
end
it { is_expected.to eq('on_success') }
end
context 'when config has `when`' do
let(:config) do
YAML.dump({
test: {
script: 'Hello World',
when: 'always'
}
})
end
it { is_expected.to eq('always') }
end
end
end
end
describe '#variables' do
let(:container_registry_enabled) { false }
let(:predefined_variables) do
[
{ key: 'CI', value: 'true', public: true },
{ key: 'GITLAB_CI', value: 'true', public: true },
{ key: 'CI_BUILD_ID', value: build.id.to_s, public: true },
{ key: 'CI_BUILD_TOKEN', value: build.token, public: false },
{ key: 'CI_BUILD_REF', value: build.sha, public: true },
{ key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true },
{ key: 'CI_BUILD_REF_NAME', value: 'master', public: true },
{ key: 'CI_BUILD_REF_SLUG', value: 'master', public: true },
{ key: 'CI_BUILD_NAME', value: 'test', public: true },
{ key: 'CI_BUILD_STAGE', value: 'test', public: true },
{ key: 'CI_SERVER_NAME', value: 'GitLab', public: true },
{ key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true },
{ key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true },
{ key: 'CI_PROJECT_ID', value: project.id.to_s, public: true },
{ key: 'CI_PROJECT_NAME', value: project.path, public: true },
{ key: 'CI_PROJECT_PATH', value: project.path_with_namespace, public: true },
{ key: 'CI_PROJECT_NAMESPACE', value: project.namespace.path, public: true },
{ key: 'CI_PROJECT_URL', value: project.web_url, public: true },
{ key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true }
]
end
before do
stub_container_registry_config(enabled: container_registry_enabled, host_port: 'registry.example.com')
end
subject { build.variables }
context 'returns variables' do
before do
build.yaml_variables = []
end
it { is_expected.to eq(predefined_variables) }
end
context 'when build has user' do
let(:user) { create(:user, username: 'starter') }
let(:user_variables) do
[
{ key: 'GITLAB_USER_ID', value: user.id.to_s, public: true },
{ key: 'GITLAB_USER_EMAIL', value: user.email, public: true }
]
end
before do
build.update_attributes(user: user)
end
it { user_variables.each { |v| is_expected.to include(v) } }
end
context 'when build has an environment' do
before do
build.update(environment: 'production')
create(:environment, project: build.project, name: 'production', slug: 'prod-slug')
end
let(:environment_variables) do
[
{ key: 'CI_ENVIRONMENT_NAME', value: 'production', public: true },
{ key: 'CI_ENVIRONMENT_SLUG', value: 'prod-slug', public: true }
]
end
it { environment_variables.each { |v| is_expected.to include(v) } }
end
context 'when build started manually' do
before do
build.update_attributes(when: :manual)
end
let(:manual_variable) do
{ key: 'CI_BUILD_MANUAL', value: 'true', public: true }
end
it { is_expected.to include(manual_variable) }
end
context 'when build is for tag' do
let(:tag_variable) do
{ key: 'CI_BUILD_TAG', value: 'master', public: true }
end
before do
build.update_attributes(tag: true)
end
it { is_expected.to include(tag_variable) }
end
context 'when secure variable is defined' do
let(:secure_variable) do
{ key: 'SECRET_KEY', value: 'secret_value', public: false }
end
before do
build.project.variables << Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value')
end
it { is_expected.to include(secure_variable) }
end
context 'when build is for triggers' do
let(:trigger) { create(:ci_trigger, project: project) }
let(:trigger_request) { create(:ci_trigger_request_with_variables, pipeline: pipeline, trigger: trigger) }
let(:user_trigger_variable) do
{ key: :TRIGGER_KEY_1, value: 'TRIGGER_VALUE_1', public: false }
end
let(:predefined_trigger_variable) do
{ key: 'CI_BUILD_TRIGGERED', value: 'true', public: true }
end
before do
build.trigger_request = trigger_request
end
it { is_expected.to include(user_trigger_variable) }
it { is_expected.to include(predefined_trigger_variable) }
end
context 'when yaml_variables are undefined' do
before do
build.yaml_variables = nil
end
context 'use from gitlab-ci.yml' do
before do
stub_ci_pipeline_yaml_file(config)
end
context 'when config is not found' do
let(:config) { nil }
it { is_expected.to eq(predefined_variables) }
end
context 'when config does not have a questioned job' do
let(:config) do
YAML.dump({
test_other: {
script: 'Hello World'
}
})
end
it { is_expected.to eq(predefined_variables) }
end
context 'when config has variables' do
let(:config) do
YAML.dump({
test: {
script: 'Hello World',
variables: {
KEY: 'value'
}
}
})
end
let(:variables) do
[{ key: 'KEY', value: 'value', public: true }]
end
it { is_expected.to eq(predefined_variables + variables) }
end
end
end
context 'when container registry is enabled' do
let(:container_registry_enabled) { true }
let(:ci_registry) do
{ key: 'CI_REGISTRY', value: 'registry.example.com', public: true }
end
let(:ci_registry_image) do
{ key: 'CI_REGISTRY_IMAGE', value: project.container_registry_repository_url, public: true }
end
context 'and is disabled for project' do
before do
project.update(container_registry_enabled: false)
end
it { is_expected.to include(ci_registry) }
it { is_expected.not_to include(ci_registry_image) }
end
context 'and is enabled for project' do
before do
project.update(container_registry_enabled: true)
end
it { is_expected.to include(ci_registry) }
it { is_expected.to include(ci_registry_image) }
end
end
context 'when runner is assigned to build' do
let(:runner) { create(:ci_runner, description: 'description', tag_list: ['docker', 'linux']) }
before do
build.update(runner: runner)
end
it { is_expected.to include({ key: 'CI_RUNNER_ID', value: runner.id.to_s, public: true }) }
it { is_expected.to include({ key: 'CI_RUNNER_DESCRIPTION', value: 'description', public: true }) }
it { is_expected.to include({ key: 'CI_RUNNER_TAGS', value: 'docker, linux', public: true }) }
end
context 'when build is for a deployment' do
let(:deployment_variable) { { key: 'KUBERNETES_TOKEN', value: 'TOKEN', public: false } }
before do
build.environment = 'production'
allow(project).to receive(:deployment_variables).and_return([deployment_variable])
end
it { is_expected.to include(deployment_variable) }
end
context 'returns variables in valid order' do
before do
allow(build).to receive(:predefined_variables) { ['predefined'] }
allow(project).to receive(:predefined_variables) { ['project'] }
allow(pipeline).to receive(:predefined_variables) { ['pipeline'] }
allow(build).to receive(:yaml_variables) { ['yaml'] }
allow(project).to receive(:secret_variables) { ['secret'] }
end
it { is_expected.to eq(%w[predefined project pipeline yaml secret]) }
end
end
end end
...@@ -243,4 +243,23 @@ describe CommitStatus, models: true do ...@@ -243,4 +243,23 @@ describe CommitStatus, models: true do
.to be_a Gitlab::Ci::Status::Success .to be_a Gitlab::Ci::Status::Success
end end
end end
describe '#sortable_name' do
tests = {
'karma' => ['karma'],
'karma 0 20' => ['karma ', 0, ' ', 20],
'karma 10 20' => ['karma ', 10, ' ', 20],
'karma 50:100' => ['karma ', 50, ':', 100],
'karma 1.10' => ['karma ', 1, '.', 10],
'karma 1.5.1' => ['karma ', 1, '.', 5, '.', 1],
'karma 1 a' => ['karma ', 1, ' a']
}
tests.each do |name, sortable_name|
it "'#{name}' sorts as '#{sortable_name}'" do
commit_status.name = name
expect(commit_status.sortable_name).to eq(sortable_name)
end
end
end
end end
require 'spec_helper' require 'spec_helper'
describe BambooService, models: true do describe BambooService, models: true, caching: true do
include ReactiveCachingHelpers
let(:bamboo_url) { 'http://gitlab.com/bamboo' }
subject(:service) do
described_class.create(
project: create(:empty_project),
properties: {
bamboo_url: bamboo_url,
username: 'mic',
password: 'password',
build_key: 'foo'
}
)
end
describe 'Associations' do describe 'Associations' do
it { is_expected.to belong_to :project } it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook } it { is_expected.to have_one :service_hook }
end end
describe 'Validations' do describe 'Validations' do
subject { service }
context 'when service is active' do context 'when service is active' do
before { subject.active = true } before { subject.active = true }
...@@ -103,90 +117,103 @@ describe BambooService, models: true do ...@@ -103,90 +117,103 @@ describe BambooService, models: true do
end end
describe '#build_page' do describe '#build_page' do
it 'returns the contents of the reactive cache' do
stub_reactive_cache(service, { build_page: 'foo' }, 'sha', 'ref')
expect(service.build_page('sha', 'ref')).to eq('foo')
end
end
describe '#commit_status' do
it 'returns the contents of the reactive cache' do
stub_reactive_cache(service, { commit_status: 'foo' }, 'sha', 'ref')
expect(service.commit_status('sha', 'ref')).to eq('foo')
end
end
describe '#calculate_reactive_cache' do
context '#build_page' do
subject { service.calculate_reactive_cache('123', 'unused')[:build_page] }
it 'returns a specific URL when status is 500' do it 'returns a specific URL when status is 500' do
stub_request(status: 500) stub_request(status: 500)
expect(service.build_page('123', 'unused')).to eq('http://gitlab.com/bamboo/browse/foo') is_expected.to eq('http://gitlab.com/bamboo/browse/foo')
end end
it 'returns a specific URL when response has no results' do it 'returns a specific URL when response has no results' do
stub_request(body: %Q({"results":{"results":{"size":"0"}}})) stub_request(body: bamboo_response(size: 0))
expect(service.build_page('123', 'unused')).to eq('http://gitlab.com/bamboo/browse/foo') is_expected.to eq('http://gitlab.com/bamboo/browse/foo')
end end
it 'returns a build URL when bamboo_url has no trailing slash' do it 'returns a build URL when bamboo_url has no trailing slash' do
stub_request(body: %Q({"results":{"results":{"result":{"planResultKey":{"key":"42"}}}}})) stub_request(body: bamboo_response)
expect(service(bamboo_url: 'http://gitlab.com/bamboo').build_page('123', 'unused')).to eq('http://gitlab.com/bamboo/browse/42') is_expected.to eq('http://gitlab.com/bamboo/browse/42')
end end
it 'returns a build URL when bamboo_url has a trailing slash' do context 'bamboo_url has trailing slash' do
stub_request(body: %Q({"results":{"results":{"result":{"planResultKey":{"key":"42"}}}}})) let(:bamboo_url) { 'http://gitlab.com/bamboo/' }
it 'returns a build URL' do
stub_request(body: bamboo_response)
expect(service(bamboo_url: 'http://gitlab.com/bamboo/').build_page('123', 'unused')).to eq('http://gitlab.com/bamboo/browse/42') is_expected.to eq('http://gitlab.com/bamboo/browse/42')
end
end end
end end
describe '#commit_status' do context '#commit_status' do
subject { service.calculate_reactive_cache('123', 'unused')[:commit_status] }
it 'sets commit status to :error when status is 500' do it 'sets commit status to :error when status is 500' do
stub_request(status: 500) stub_request(status: 500)
expect(service.commit_status('123', 'unused')).to eq(:error) is_expected.to eq(:error)
end end
it 'sets commit status to "pending" when status is 404' do it 'sets commit status to "pending" when status is 404' do
stub_request(status: 404) stub_request(status: 404)
expect(service.commit_status('123', 'unused')).to eq('pending') is_expected.to eq('pending')
end end
it 'sets commit status to "pending" when response has no results' do it 'sets commit status to "pending" when response has no results' do
stub_request(body: %Q({"results":{"results":{"size":"0"}}})) stub_request(body: %Q({"results":{"results":{"size":"0"}}}))
expect(service.commit_status('123', 'unused')).to eq('pending') is_expected.to eq('pending')
end end
it 'sets commit status to "success" when build state contains Success' do it 'sets commit status to "success" when build state contains Success' do
stub_request(build_state: 'YAY Success!') stub_request(body: bamboo_response(build_state: 'YAY Success!'))
expect(service.commit_status('123', 'unused')).to eq('success') is_expected.to eq('success')
end end
it 'sets commit status to "failed" when build state contains Failed' do it 'sets commit status to "failed" when build state contains Failed' do
stub_request(build_state: 'NO Failed!') stub_request(body: bamboo_response(build_state: 'NO Failed!'))
expect(service.commit_status('123', 'unused')).to eq('failed') is_expected.to eq('failed')
end end
it 'sets commit status to "pending" when build state contains Pending' do it 'sets commit status to "pending" when build state contains Pending' do
stub_request(build_state: 'NO Pending!') stub_request(body: bamboo_response(build_state: 'NO Pending!'))
expect(service.commit_status('123', 'unused')).to eq('pending') is_expected.to eq('pending')
end end
it 'sets commit status to :error when build state is unknown' do it 'sets commit status to :error when build state is unknown' do
stub_request(build_state: 'FOO BAR!') stub_request(body: bamboo_response(build_state: 'FOO BAR!'))
expect(service.commit_status('123', 'unused')).to eq(:error) is_expected.to eq(:error)
end end
end end
def service(bamboo_url: 'http://gitlab.com/bamboo')
described_class.create(
project: create(:empty_project),
properties: {
bamboo_url: bamboo_url,
username: 'mic',
password: 'password',
build_key: 'foo'
}
)
end end
def stub_request(status: 200, body: nil, build_state: 'success') def stub_request(status: 200, body: nil)
bamboo_full_url = 'http://mic:password@gitlab.com/bamboo/rest/api/latest/result?label=123&os_authType=basic' bamboo_full_url = 'http://mic:password@gitlab.com/bamboo/rest/api/latest/result?label=123&os_authType=basic'
body ||= %Q({"results":{"results":{"result":{"buildState":"#{build_state}"}}}})
WebMock.stub_request(:get, bamboo_full_url).to_return( WebMock.stub_request(:get, bamboo_full_url).to_return(
status: status, status: status,
...@@ -194,4 +221,8 @@ describe BambooService, models: true do ...@@ -194,4 +221,8 @@ describe BambooService, models: true do
body: body body: body
) )
end end
def bamboo_response(result_key: 42, build_state: 'success', size: 1)
%Q({"results":{"results":{"size":"#{size}","result":{"buildState":"#{build_state}","planResultKey":{"key":"#{result_key}"}}}}})
end
end end
require 'spec_helper' require 'spec_helper'
describe BuildkiteService, models: true do describe BuildkiteService, models: true, caching: true do
include ReactiveCachingHelpers
let(:project) { create(:empty_project) }
subject(:service) do
described_class.create(
project: project,
properties: {
service_hook: true,
project_url: 'https://buildkite.com/account-name/example-project',
token: 'secret-sauce-webhook-token:secret-sauce-status-token'
}
)
end
describe 'Associations' do describe 'Associations' do
it { is_expected.to belong_to :project } it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook } it { is_expected.to have_one :service_hook }
...@@ -25,21 +40,12 @@ describe BuildkiteService, models: true do ...@@ -25,21 +40,12 @@ describe BuildkiteService, models: true do
describe 'commits methods' do describe 'commits methods' do
before do before do
@project = Project.new allow(project).to receive(:default_branch).and_return('default-brancho')
allow(@project).to receive(:default_branch).and_return('default-brancho')
@service = BuildkiteService.new
allow(@service).to receive_messages(
project: @project,
service_hook: true,
project_url: 'https://buildkite.com/account-name/example-project',
token: 'secret-sauce-webhook-token:secret-sauce-status-token'
)
end end
describe '#webhook_url' do describe '#webhook_url' do
it 'returns the webhook url' do it 'returns the webhook url' do
expect(@service.webhook_url).to eq( expect(service.webhook_url).to eq(
'https://webhook.buildkite.com/deliver/secret-sauce-webhook-token' 'https://webhook.buildkite.com/deliver/secret-sauce-webhook-token'
) )
end end
...@@ -47,7 +53,7 @@ describe BuildkiteService, models: true do ...@@ -47,7 +53,7 @@ describe BuildkiteService, models: true do
describe '#commit_status_path' do describe '#commit_status_path' do
it 'returns the correct status page' do it 'returns the correct status page' do
expect(@service.commit_status_path('2ab7834c')).to eq( expect(service.commit_status_path('2ab7834c')).to eq(
'https://gitlab.buildkite.com/status/secret-sauce-status-token.json?commit=2ab7834c' 'https://gitlab.buildkite.com/status/secret-sauce-status-token.json?commit=2ab7834c'
) )
end end
...@@ -55,10 +61,53 @@ describe BuildkiteService, models: true do ...@@ -55,10 +61,53 @@ describe BuildkiteService, models: true do
describe '#build_page' do describe '#build_page' do
it 'returns the correct build page' do it 'returns the correct build page' do
expect(@service.build_page('2ab7834c', nil)).to eq( expect(service.build_page('2ab7834c', nil)).to eq(
'https://buildkite.com/account-name/example-project/builds?commit=2ab7834c' 'https://buildkite.com/account-name/example-project/builds?commit=2ab7834c'
) )
end end
end end
describe '#commit_status' do
it 'returns the contents of the reactive cache' do
stub_reactive_cache(service, { commit_status: 'foo' }, 'sha', 'ref')
expect(service.commit_status('sha', 'ref')).to eq('foo')
end
end
describe '#calculate_reactive_cache' do
context '#commit_status' do
subject { service.calculate_reactive_cache('123', 'unused')[:commit_status] }
it 'sets commit status to :error when status is 500' do
stub_request(status: 500)
is_expected.to eq(:error)
end
it 'sets commit status to :error when status is 404' do
stub_request(status: 404)
is_expected.to eq(:error)
end
it 'passes through build status untouched when status is 200' do
stub_request(body: %Q({"status":"Great Success"}))
is_expected.to eq('Great Success')
end
end
end
end
def stub_request(status: 200, body: nil)
body ||= %Q({"status":"success"})
buildkite_full_url = 'https://gitlab.buildkite.com/status/secret-sauce-status-token.json?commit=123'
WebMock.stub_request(:get, buildkite_full_url).to_return(
status: status,
headers: { 'Content-Type' => 'application/json' },
body: body
)
end end
end end
require 'spec_helper' require 'spec_helper'
describe DroneCiService, models: true do describe DroneCiService, models: true, caching: true do
include ReactiveCachingHelpers
describe 'associations' do describe 'associations' do
it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:project) }
it { is_expected.to have_one(:service_hook) } it { is_expected.to have_one(:service_hook) }
...@@ -33,6 +35,10 @@ describe DroneCiService, models: true do ...@@ -33,6 +35,10 @@ describe DroneCiService, models: true do
let(:token) { 'secret' } let(:token) { 'secret' }
let(:iid) { rand(1..9999) } let(:iid) { rand(1..9999) }
# URL's
let(:build_page) { "#{drone_url}/gitlab/#{path}/redirect/commits/#{sha}?branch=#{branch}" }
let(:commit_status_path) { "#{drone_url}/gitlab/#{path}/commits/#{sha}?branch=#{branch}&access_token=#{token}" }
before(:each) do before(:each) do
allow(drone).to receive_messages( allow(drone).to receive_messages(
project_id: project.id, project_id: project.id,
...@@ -42,22 +48,66 @@ describe DroneCiService, models: true do ...@@ -42,22 +48,66 @@ describe DroneCiService, models: true do
token: token token: token
) )
end end
def stub_request(status: 200, body: nil)
body ||= %Q({"status":"success"})
WebMock.stub_request(:get, commit_status_path).to_return(
status: status,
headers: { 'Content-Type' => 'application/json' },
body: body
)
end
end end
describe "service page/path methods" do describe "service page/path methods" do
include_context :drone_ci_service include_context :drone_ci_service
# URL's it { expect(drone.build_page(sha, branch)).to eq(build_page) }
let(:commit_page) { "#{drone_url}/gitlab/#{path}/redirect/commits/#{sha}?branch=#{branch}" }
let(:merge_request_page) { "#{drone_url}/gitlab/#{path}/redirect/pulls/#{iid}" }
let(:commit_status_path) { "#{drone_url}/gitlab/#{path}/commits/#{sha}?branch=#{branch}&access_token=#{token}" }
let(:merge_request_status_path) { "#{drone_url}/gitlab/#{path}/pulls/#{iid}?access_token=#{token}" }
it { expect(drone.build_page(sha, branch)).to eq(commit_page) }
it { expect(drone.commit_page(sha, branch)).to eq(commit_page) }
it { expect(drone.merge_request_page(iid, sha, branch)).to eq(merge_request_page) }
it { expect(drone.commit_status_path(sha, branch)).to eq(commit_status_path) } it { expect(drone.commit_status_path(sha, branch)).to eq(commit_status_path) }
it { expect(drone.merge_request_status_path(iid, sha, branch)).to eq(merge_request_status_path) } end
describe '#commit_status' do
include_context :drone_ci_service
it 'returns the contents of the reactive cache' do
stub_reactive_cache(drone, { commit_status: 'foo' }, 'sha', 'ref')
expect(drone.commit_status('sha', 'ref')).to eq('foo')
end
end
describe '#calculate_reactive_cache' do
include_context :drone_ci_service
context '#commit_status' do
subject { drone.calculate_reactive_cache(sha, branch)[:commit_status] }
it 'sets commit status to :error when status is 500' do
stub_request(status: 500)
is_expected.to eq(:error)
end
it 'sets commit status to :error when status is 404' do
stub_request(status: 404)
is_expected.to eq(:error)
end
{ "killed" => :canceled,
"failure" => :failed,
"error" => :failed,
"success" => "success",
}.each do |drone_status, our_status|
it "sets commit status to #{our_status.inspect} when returned status is #{drone_status.inspect}" do
stub_request(body: %Q({"status":"#{drone_status}"}))
is_expected.to eq(our_status)
end
end
end
end end
describe "execute" do describe "execute" do
......
require 'spec_helper' require 'spec_helper'
describe TeamcityService, models: true do describe TeamcityService, models: true, caching: true do
include ReactiveCachingHelpers
let(:teamcity_url) { 'http://gitlab.com/teamcity' }
subject(:service) do
described_class.create(
project: create(:empty_project),
properties: {
teamcity_url: teamcity_url,
username: 'mic',
password: 'password',
build_type: 'foo'
}
)
end
describe 'Associations' do describe 'Associations' do
it { is_expected.to belong_to :project } it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook } it { is_expected.to have_one :service_hook }
end end
describe 'Validations' do describe 'Validations' do
subject { service }
context 'when service is active' do context 'when service is active' do
before { subject.active = true } before { subject.active = true }
...@@ -103,73 +117,87 @@ describe TeamcityService, models: true do ...@@ -103,73 +117,87 @@ describe TeamcityService, models: true do
end end
describe '#build_page' do describe '#build_page' do
it 'returns the contents of the reactive cache' do
stub_reactive_cache(service, { build_page: 'foo' }, 'sha', 'ref')
expect(service.build_page('sha', 'ref')).to eq('foo')
end
end
describe '#commit_status' do
it 'returns the contents of the reactive cache' do
stub_reactive_cache(service, { commit_status: 'foo' }, 'sha', 'ref')
expect(service.commit_status('sha', 'ref')).to eq('foo')
end
end
describe '#calculate_reactive_cache' do
context 'build_page' do
subject { service.calculate_reactive_cache('123', 'unused')[:build_page] }
it 'returns a specific URL when status is 500' do it 'returns a specific URL when status is 500' do
stub_request(status: 500) stub_request(status: 500)
expect(service.build_page('123', 'unused')).to eq('http://gitlab.com/teamcity/viewLog.html?buildTypeId=foo') is_expected.to eq('http://gitlab.com/teamcity/viewLog.html?buildTypeId=foo')
end end
it 'returns a build URL when teamcity_url has no trailing slash' do it 'returns a build URL when teamcity_url has no trailing slash' do
stub_request(body: %Q({"build":{"id":"666"}})) stub_request(body: %Q({"build":{"id":"666"}}))
expect(service(teamcity_url: 'http://gitlab.com/teamcity').build_page('123', 'unused')).to eq('http://gitlab.com/teamcity/viewLog.html?buildId=666&buildTypeId=foo') is_expected.to eq('http://gitlab.com/teamcity/viewLog.html?buildId=666&buildTypeId=foo')
end end
it 'returns a build URL when teamcity_url has a trailing slash' do context 'teamcity_url has trailing slash' do
let(:teamcity_url) { 'http://gitlab.com/teamcity/' }
it 'returns a build URL' do
stub_request(body: %Q({"build":{"id":"666"}})) stub_request(body: %Q({"build":{"id":"666"}}))
expect(service(teamcity_url: 'http://gitlab.com/teamcity/').build_page('123', 'unused')).to eq('http://gitlab.com/teamcity/viewLog.html?buildId=666&buildTypeId=foo') is_expected.to eq('http://gitlab.com/teamcity/viewLog.html?buildId=666&buildTypeId=foo')
end
end end
end end
describe '#commit_status' do context 'commit_status' do
subject { service.calculate_reactive_cache('123', 'unused')[:commit_status] }
it 'sets commit status to :error when status is 500' do it 'sets commit status to :error when status is 500' do
stub_request(status: 500) stub_request(status: 500)
expect(service.commit_status('123', 'unused')).to eq(:error) is_expected.to eq(:error)
end end
it 'sets commit status to "pending" when status is 404' do it 'sets commit status to "pending" when status is 404' do
stub_request(status: 404) stub_request(status: 404)
expect(service.commit_status('123', 'unused')).to eq('pending') is_expected.to eq('pending')
end end
it 'sets commit status to "success" when build status contains SUCCESS' do it 'sets commit status to "success" when build status contains SUCCESS' do
stub_request(build_status: 'YAY SUCCESS!') stub_request(build_status: 'YAY SUCCESS!')
expect(service.commit_status('123', 'unused')).to eq('success') is_expected.to eq('success')
end end
it 'sets commit status to "failed" when build status contains FAILURE' do it 'sets commit status to "failed" when build status contains FAILURE' do
stub_request(build_status: 'NO FAILURE!') stub_request(build_status: 'NO FAILURE!')
expect(service.commit_status('123', 'unused')).to eq('failed') is_expected.to eq('failed')
end end
it 'sets commit status to "pending" when build status contains Pending' do it 'sets commit status to "pending" when build status contains Pending' do
stub_request(build_status: 'NO Pending!') stub_request(build_status: 'NO Pending!')
expect(service.commit_status('123', 'unused')).to eq('pending') is_expected.to eq('pending')
end end
it 'sets commit status to :error when build status is unknown' do it 'sets commit status to :error when build status is unknown' do
stub_request(build_status: 'FOO BAR!') stub_request(build_status: 'FOO BAR!')
expect(service.commit_status('123', 'unused')).to eq(:error) is_expected.to eq(:error)
end end
end end
def service(teamcity_url: 'http://gitlab.com/teamcity')
described_class.create(
project: create(:empty_project),
properties: {
teamcity_url: teamcity_url,
username: 'mic',
password: 'password',
build_type: 'foo'
}
)
end end
def stub_request(status: 200, body: nil, build_status: 'success') def stub_request(status: 200, body: nil, build_status: 'success')
......
...@@ -50,6 +50,8 @@ describe API::Issues, api: true do ...@@ -50,6 +50,8 @@ describe API::Issues, api: true do
end end
let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) } let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) }
let(:no_milestone_title) { URI.escape(Milestone::None.title) }
before do before do
project.team << [user, :reporter] project.team << [user, :reporter]
project.team << [guest, :guest] project.team << [guest, :guest]
...@@ -107,6 +109,7 @@ describe API::Issues, api: true do ...@@ -107,6 +109,7 @@ describe API::Issues, api: true do
it 'returns an array of labeled issues when at least one label matches' do it 'returns an array of labeled issues when at least one label matches' do
get api("/issues?labels=#{label.title},foo,bar", user) get api("/issues?labels=#{label.title},foo,bar", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.length).to eq(1) expect(json_response.length).to eq(1)
...@@ -136,6 +139,51 @@ describe API::Issues, api: true do ...@@ -136,6 +139,51 @@ describe API::Issues, api: true do
expect(json_response.length).to eq(0) expect(json_response.length).to eq(0)
end end
it 'returns an empty array if no issue matches milestone' do
get api("/issues?milestone=#{empty_milestone.title}", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
it 'returns an empty array if milestone does not exist' do
get api("/issues?milestone=foo", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
it 'returns an array of issues in given milestone' do
get api("/issues?milestone=#{milestone.title}", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
expect(json_response.first['id']).to eq(issue.id)
expect(json_response.second['id']).to eq(closed_issue.id)
end
it 'returns an array of issues matching state in milestone' do
get api("/issues?milestone=#{milestone.title}"\
'&state=closed', user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(closed_issue.id)
end
it 'returns an array of issues with no milestone' do
get api("/issues?milestone=#{no_milestone_title}", author)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(confidential_issue.id)
end
it 'sorts by created_at descending by default' do it 'sorts by created_at descending by default' do
get api('/issues', user) get api('/issues', user)
response_dates = json_response.map { |issue| issue['created_at'] } response_dates = json_response.map { |issue| issue['created_at'] }
...@@ -318,6 +366,15 @@ describe API::Issues, api: true do ...@@ -318,6 +366,15 @@ describe API::Issues, api: true do
expect(json_response.first['id']).to eq(group_closed_issue.id) expect(json_response.first['id']).to eq(group_closed_issue.id)
end end
it 'returns an array of issues with no milestone' do
get api("#{base_url}?milestone=#{no_milestone_title}", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(group_confidential_issue.id)
end
it 'sorts by created_at descending by default' do it 'sorts by created_at descending by default' do
get api(base_url, user) get api(base_url, user)
response_dates = json_response.map { |issue| issue['created_at'] } response_dates = json_response.map { |issue| issue['created_at'] }
...@@ -357,7 +414,6 @@ describe API::Issues, api: true do ...@@ -357,7 +414,6 @@ describe API::Issues, api: true do
describe "GET /projects/:id/issues" do describe "GET /projects/:id/issues" do
let(:base_url) { "/projects/#{project.id}" } let(:base_url) { "/projects/#{project.id}" }
let(:title) { milestone.title }
it "returns 404 on private projects for other users" do it "returns 404 on private projects for other users" do
private_project = create(:empty_project, :private) private_project = create(:empty_project, :private)
...@@ -433,8 +489,9 @@ describe API::Issues, api: true do ...@@ -433,8 +489,9 @@ describe API::Issues, api: true do
expect(json_response.first['labels']).to eq([label.title]) expect(json_response.first['labels']).to eq([label.title])
end end
it 'returns an array of labeled project issues when at least one label matches' do it 'returns an array of labeled project issues where all labels match' do
get api("#{base_url}/issues?labels=#{label.title},foo,bar", user) get api("#{base_url}/issues?labels=#{label.title},foo,bar", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.length).to eq(1) expect(json_response.length).to eq(1)
...@@ -463,7 +520,8 @@ describe API::Issues, api: true do ...@@ -463,7 +520,8 @@ describe API::Issues, api: true do
end end
it 'returns an array of issues in given milestone' do it 'returns an array of issues in given milestone' do
get api("#{base_url}/issues?milestone=#{title}", user) get api("#{base_url}/issues?milestone=#{milestone.title}", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.length).to eq(2) expect(json_response.length).to eq(2)
...@@ -480,6 +538,15 @@ describe API::Issues, api: true do ...@@ -480,6 +538,15 @@ describe API::Issues, api: true do
expect(json_response.first['id']).to eq(closed_issue.id) expect(json_response.first['id']).to eq(closed_issue.id)
end end
it 'returns an array of issues with no milestone' do
get api("#{base_url}/issues?milestone=#{no_milestone_title}", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(confidential_issue.id)
end
it 'sorts by created_at descending by default' do it 'sorts by created_at descending by default' do
get api("#{base_url}/issues", user) get api("#{base_url}/issues", user)
response_dates = json_response.map { |issue| issue['created_at'] } response_dates = json_response.map { |issue| issue['created_at'] }
...@@ -547,12 +614,21 @@ describe API::Issues, api: true do ...@@ -547,12 +614,21 @@ describe API::Issues, api: true do
it 'returns a project issue by iid' do it 'returns a project issue by iid' do
get api("/projects/#{project.id}/issues?iid=#{issue.iid}", user) get api("/projects/#{project.id}/issues?iid=#{issue.iid}", user)
expect(response.status).to eq 200 expect(response.status).to eq 200
expect(json_response.length).to eq 1
expect(json_response.first['title']).to eq issue.title expect(json_response.first['title']).to eq issue.title
expect(json_response.first['id']).to eq issue.id expect(json_response.first['id']).to eq issue.id
expect(json_response.first['iid']).to eq issue.iid expect(json_response.first['iid']).to eq issue.iid
end end
it 'returns an empty array for an unknown project issue iid' do
get api("/projects/#{project.id}/issues?iid=#{issue.iid + 10}", user)
expect(response.status).to eq 200
expect(json_response.length).to eq 0
end
it "returns 404 if issue id not found" do it "returns 404 if issue id not found" do
get api("/projects/#{project.id}/issues/54321", user) get api("/projects/#{project.id}/issues/54321", user)
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
......
...@@ -33,10 +33,12 @@ describe CommitEntity do ...@@ -33,10 +33,12 @@ describe CommitEntity do
it 'contains path to commit' do it 'contains path to commit' do
expect(subject).to include(:commit_path) expect(subject).to include(:commit_path)
expect(subject[:commit_path]).to include "commit/#{commit.id}"
end end
it 'contains URL to commit' do it 'contains URL to commit' do
expect(subject).to include(:commit_url) expect(subject).to include(:commit_url)
expect(subject[:commit_path]).to include "commit/#{commit.id}"
end end
it 'needs to receive project in the request' do it 'needs to receive project in the request' do
......
...@@ -3,31 +3,35 @@ module ReactiveCachingHelpers ...@@ -3,31 +3,35 @@ module ReactiveCachingHelpers
([subject.class.reactive_cache_key.call(subject)].flatten + qualifiers).join(':') ([subject.class.reactive_cache_key.call(subject)].flatten + qualifiers).join(':')
end end
def stub_reactive_cache(subject = nil, data = nil) def alive_reactive_cache_key(subject, *qualifiers)
reactive_cache_key(subject, *(qualifiers + ['alive']))
end
def stub_reactive_cache(subject = nil, data = nil, *qualifiers)
allow(ReactiveCachingWorker).to receive(:perform_async) allow(ReactiveCachingWorker).to receive(:perform_async)
allow(ReactiveCachingWorker).to receive(:perform_in) allow(ReactiveCachingWorker).to receive(:perform_in)
write_reactive_cache(subject, data) if data write_reactive_cache(subject, data, *qualifiers) if data
end end
def read_reactive_cache(subject) def read_reactive_cache(subject, *qualifiers)
Rails.cache.read(reactive_cache_key(subject)) Rails.cache.read(reactive_cache_key(subject, *qualifiers))
end end
def write_reactive_cache(subject, data) def write_reactive_cache(subject, data, *qualifiers)
start_reactive_cache_lifetime(subject) start_reactive_cache_lifetime(subject, *qualifiers)
Rails.cache.write(reactive_cache_key(subject), data) Rails.cache.write(reactive_cache_key(subject, *qualifiers), data)
end end
def reactive_cache_alive?(subject) def reactive_cache_alive?(subject, *qualifiers)
Rails.cache.read(reactive_cache_key(subject, 'alive')) Rails.cache.read(alive_reactive_cache_key(subject, *qualifiers))
end end
def invalidate_reactive_cache(subject) def invalidate_reactive_cache(subject, *qualifiers)
Rails.cache.delete(reactive_cache_key(subject, 'alive')) Rails.cache.delete(alive_reactive_cache_key(subject, *qualifiers))
end end
def start_reactive_cache_lifetime(subject) def start_reactive_cache_lifetime(subject, *qualifiers)
Rails.cache.write(reactive_cache_key(subject, 'alive'), true) Rails.cache.write(alive_reactive_cache_key(subject, *qualifiers), true)
end end
def expect_reactive_cache_update_queued(subject) def expect_reactive_cache_update_queued(subject)
......
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