Commit 7ca57c59 authored by Mike Greiling's avatar Mike Greiling

Merge branch 'master' into webpack

* master: (63 commits)
  Use `add_$role` helper in snippets specs
  removes old css class from everywhere
  Fixes broken build: Use jquery to get the element position in the page
  Check public snippets for spam
  Keep snippet visibility on error
  Update pipeline and commit URL and text on CI status change
  Support non-ASCII characters in GFM autocomplete
  Active tense test coverage
  Fix filtered search manager spec teaspoon error
  Reduce the number of loops that Cycle Analytics specs use
  Remove unnecessary returns / unset variables from the CoffeeScript -> JS conversion.
  update spec
  Change the reply shortcut to focus the field even without a selection.
  use destroy_all
  Remove settings cog from within admin scroll tabs; keep links centered
  add changelog
  remove old project members from project
  add spec replicating validation error
  Improve styling of the new issue message
  Don't capitalize environment name in show page
  ...
parents 5a099315 4615d099
...@@ -2,6 +2,17 @@ ...@@ -2,6 +2,17 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 8.16.3 (2017-01-27)
- Add caching of droplab ajax requests. !8725
- Fix access to the wiki code via HTTP when repository feature disabled. !8758
- Revert 3f17f29a. !8785
- Fix race conditions for AuthorizedProjectsWorker.
- Fix autocomplete initial undefined state.
- Fix Error 500 when repositories contain annotated tags pointing to blobs.
- Fix /explore sorting.
- Fixed label dropdown toggle text not correctly updating.
## 8.16.2 (2017-01-25) ## 8.16.2 (2017-01-25)
- allow issue filter bar to be operated with mouse only. !8681 - allow issue filter bar to be operated with mouse only. !8681
......
/* eslint-disable wrap-iife, func-names, space-before-function-paren, prefer-arrow-callback, vars-on-top, no-var, max-len */ /* eslint-disable wrap-iife, func-names, space-before-function-paren, prefer-arrow-callback, vars-on-top, no-var, max-len */
(function(w) { (function(w) {
$(function() { $(function() {
var toggleContainer = function(container, /* optional */toggleState) {
var $container = $(container);
$container
.find('.js-toggle-button .fa')
.toggleClass('fa-chevron-up', toggleState)
.toggleClass('fa-chevron-down', toggleState !== undefined ? !toggleState : undefined);
$container
.find('.js-toggle-content')
.toggle(toggleState);
};
// Toggle button. Show/hide content inside parent container. // Toggle button. Show/hide content inside parent container.
// Button does not change visibility. If button has icon - it changes chevron style. // Button does not change visibility. If button has icon - it changes chevron style.
// //
...@@ -10,14 +23,7 @@ ...@@ -10,14 +23,7 @@
// //
$('body').on('click', '.js-toggle-button', function(e) { $('body').on('click', '.js-toggle-button', function(e) {
e.preventDefault(); e.preventDefault();
$(this) toggleContainer($(this).closest('.js-toggle-container'));
.find('.fa')
.toggleClass('fa-chevron-down fa-chevron-up')
.end()
.closest('.js-toggle-container')
.find('.js-toggle-content')
.toggle()
;
}); });
// If we're accessing a permalink, ensure it is not inside a // If we're accessing a permalink, ensure it is not inside a
...@@ -26,8 +32,8 @@ ...@@ -26,8 +32,8 @@
var anchor = hash && document.getElementById(hash); var anchor = hash && document.getElementById(hash);
var container = anchor && $(anchor).closest('.js-toggle-container'); var container = anchor && $(anchor).closest('.js-toggle-container');
if (container && container.find('.js-toggle-content').is(':hidden')) { if (container) {
container.find('.js-toggle-button').trigger('click'); toggleContainer(container, true);
anchor.scrollIntoView(); anchor.scrollIntoView();
} }
}); });
......
...@@ -182,7 +182,7 @@ require('./environment_item'); ...@@ -182,7 +182,7 @@ require('./environment_item');
<th class="environments-deploy">Last deployment</th> <th class="environments-deploy">Last deployment</th>
<th class="environments-build">Build</th> <th class="environments-build">Build</th>
<th class="environments-commit">Commit</th> <th class="environments-commit">Commit</th>
<th class="environments-date">Created</th> <th class="environments-date">Updated</th>
<th class="hidden-xs environments-actions"></th> <th class="hidden-xs environments-actions"></th>
</tr> </tr>
</thead> </thead>
......
...@@ -39,8 +39,15 @@ require('./filtered_search_dropdown'); ...@@ -39,8 +39,15 @@ require('./filtered_search_dropdown');
getSearchInput() { getSearchInput() {
const query = gl.DropdownUtils.getSearchInput(this.input); const query = gl.DropdownUtils.getSearchInput(this.input);
const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query); const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query);
let value = lastToken.value || '';
return lastToken.value || ''; // Removes the first character if it is a quotation so that we can search
// with multiple words
if (value[0] === '"' || value[0] === '\'') {
value = value.slice(1);
}
return value;
} }
init() { init() {
......
...@@ -83,12 +83,12 @@ ...@@ -83,12 +83,12 @@
_a = decodeURI("%C3%80"); _a = decodeURI("%C3%80");
_y = decodeURI("%C3%BF"); _y = decodeURI("%C3%BF");
regexp = new RegExp("^(?:\\B|[^a-zA-Z0-9_" + atSymbolsWithoutBar + "]|\\s)" + flag + "(?![" + atSymbolsWithBar + "])([A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]*)$", 'gi'); regexp = new RegExp("^(?:\\B|[^a-zA-Z0-9_" + atSymbolsWithoutBar + "]|\\s)" + flag + "(?![" + atSymbolsWithBar + "])(([A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]|[^\\x00-\\x7a])*)$", 'gi');
match = regexp.exec(subtext); match = regexp.exec(subtext);
if (match) { if (match) {
return match[2] || match[1]; return (match[1] || match[1] === "") ? match[1] : match[2];
} else { } else {
return null; return null;
} }
......
...@@ -162,6 +162,7 @@ ...@@ -162,6 +162,7 @@
w.gl.utils.getSelectedFragment = () => { w.gl.utils.getSelectedFragment = () => {
const selection = window.getSelection(); const selection = window.getSelection();
if (selection.rangeCount === 0) return null;
const documentFragment = selection.getRangeAt(0).cloneContents(); const documentFragment = selection.getRangeAt(0).cloneContents();
if (documentFragment.textContent.length === 0) return null; if (documentFragment.textContent.length === 0) return null;
......
...@@ -156,12 +156,22 @@ require('./smart_interval'); ...@@ -156,12 +156,22 @@ require('./smart_interval');
return; return;
} }
if (data.environments && data.environments.length) _this.renderEnvironments(data.environments); if (data.environments && data.environments.length) _this.renderEnvironments(data.environments);
if (data.status !== _this.opts.ci_status && (data.status != null)) { if (data.status !== _this.opts.ci_status ||
data.sha !== _this.opts.ci_sha ||
data.pipeline !== _this.opts.ci_pipeline) {
_this.opts.ci_status = data.status; _this.opts.ci_status = data.status;
_this.showCIStatus(data.status); _this.showCIStatus(data.status);
if (data.coverage) { if (data.coverage) {
_this.showCICoverage(data.coverage); _this.showCICoverage(data.coverage);
} }
if (data.pipeline) {
_this.opts.ci_pipeline = data.pipeline;
_this.updatePipelineUrls(data.pipeline);
}
if (data.sha) {
_this.opts.ci_sha = data.sha;
_this.updateCommitUrls(data.sha);
}
if (showNotification) { if (showNotification) {
status = _this.ciLabelForStatus(data.status); status = _this.ciLabelForStatus(data.status);
if (status === "preparing") { if (status === "preparing") {
...@@ -250,6 +260,16 @@ require('./smart_interval'); ...@@ -250,6 +260,16 @@ require('./smart_interval');
return $('.js-merge-button,.accept-action .dropdown-toggle').removeClass('btn-danger btn-info btn-create').addClass(css_class); return $('.js-merge-button,.accept-action .dropdown-toggle').removeClass('btn-danger btn-info btn-create').addClass(css_class);
}; };
MergeRequestWidget.prototype.updatePipelineUrls = function(id) {
const pipelineUrl = this.opts.pipeline_path;
$('.pipeline').text(`#${id}`).attr('href', [pipelineUrl, id].join('/'));
};
MergeRequestWidget.prototype.updateCommitUrls = function(id) {
const commitsUrl = this.opts.commits_path;
$('.js-commit-link').text(`#${id}`).attr('href', [commitsUrl, id].join('/'));
};
return MergeRequestWidget; return MergeRequestWidget;
})(); })();
})(window.gl || (window.gl = {})); })(window.gl || (window.gl = {}));
...@@ -39,17 +39,20 @@ require('./shortcuts_navigation'); ...@@ -39,17 +39,20 @@ require('./shortcuts_navigation');
} }
ShortcutsIssuable.prototype.replyWithSelectedText = function() { ShortcutsIssuable.prototype.replyWithSelectedText = function() {
var quote, replyField, documentFragment, selected, separator; var quote, documentFragment, selected, separator;
var replyField = $('.js-main-target-form #note_note');
documentFragment = window.gl.utils.getSelectedFragment(); documentFragment = window.gl.utils.getSelectedFragment();
if (!documentFragment) return; if (!documentFragment) {
replyField.focus();
return;
}
// If the documentFragment contains more than just Markdown, don't copy as GFM. // If the documentFragment contains more than just Markdown, don't copy as GFM.
if (documentFragment.querySelector('.md, .wiki')) return; if (documentFragment.querySelector('.md, .wiki')) return;
selected = window.gl.CopyAsGFM.nodeToGFM(documentFragment); selected = window.gl.CopyAsGFM.nodeToGFM(documentFragment);
replyField = $('.js-main-target-form #note_note');
if (selected.trim() === "") { if (selected.trim() === "") {
return; return;
} }
......
...@@ -330,10 +330,6 @@ ...@@ -330,10 +330,6 @@
} }
} }
.btn-file-option {
background: linear-gradient(180deg, $white-light 25%, $gray-light 100%);
}
.btn-build { .btn-build {
margin-left: 10px; margin-left: 10px;
......
...@@ -58,3 +58,9 @@ ...@@ -58,3 +58,9 @@
fill: $gl-text-color; fill: $gl-text-color;
} }
} }
.icon-link {
&:hover {
text-decoration: none;
}
}
...@@ -294,16 +294,18 @@ ...@@ -294,16 +294,18 @@
.container-fluid { .container-fluid {
position: relative; position: relative;
.nav-control {
@media (max-width: $screen-sm-max) {
margin-right: 75px;
}
}
} }
.controls { .controls {
float: right; float: right;
padding: 7px 0 0; padding: 7px 0 0;
@media (max-width: $screen-sm-max) {
display: none;
}
i { i {
color: $layout-link-gray; color: $layout-link-gray;
} }
...@@ -361,6 +363,7 @@ ...@@ -361,6 +363,7 @@
.fade-left { .fade-left {
@include fade(right, $gray-light); @include fade(right, $gray-light);
left: -5px; left: -5px;
text-align: center;
.fa { .fa {
left: -7px; left: -7px;
......
...@@ -7,7 +7,7 @@ module SpammableActions ...@@ -7,7 +7,7 @@ module SpammableActions
def mark_as_spam def mark_as_spam
if SpamService.new(spammable).mark_as_spam! if SpamService.new(spammable).mark_as_spam!
redirect_to spammable, notice: "#{spammable.class} was submitted to Akismet successfully." redirect_to spammable, notice: "#{spammable.spammable_entity_type.titlecase} was submitted to Akismet successfully."
else else
redirect_to spammable, alert: 'Error with Akismet. Please check the logs for more info.' redirect_to spammable, alert: 'Error with Akismet. Please check the logs for more info.'
end end
......
...@@ -84,7 +84,7 @@ class GroupsController < Groups::ApplicationController ...@@ -84,7 +84,7 @@ class GroupsController < Groups::ApplicationController
if Groups::UpdateService.new(@group, current_user, group_params).execute if Groups::UpdateService.new(@group, current_user, group_params).execute
redirect_to edit_group_path(@group), notice: "Group '#{@group.name}' was successfully updated." redirect_to edit_group_path(@group), notice: "Group '#{@group.name}' was successfully updated."
else else
@group.reset_path! @group.restore_path!
render action: "edit" render action: "edit"
end end
......
...@@ -434,7 +434,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -434,7 +434,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
title: merge_request.title, title: merge_request.title,
sha: (merge_request.diff_head_commit.short_id if merge_request.diff_head_sha), sha: (merge_request.diff_head_commit.short_id if merge_request.diff_head_sha),
status: status, status: status,
coverage: coverage coverage: coverage,
pipeline: pipeline.try(:id)
} }
render json: response render json: response
......
class Projects::SnippetsController < Projects::ApplicationController class Projects::SnippetsController < Projects::ApplicationController
include ToggleAwardEmoji include ToggleAwardEmoji
include SpammableActions
before_action :module_enabled before_action :module_enabled
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji] before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam]
# Allow read any snippet # Allow read any snippet
before_action :authorize_read_project_snippet!, except: [:new, :create, :index] before_action :authorize_read_project_snippet!, except: [:new, :create, :index]
...@@ -36,8 +37,8 @@ class Projects::SnippetsController < Projects::ApplicationController ...@@ -36,8 +37,8 @@ class Projects::SnippetsController < Projects::ApplicationController
end end
def create def create
@snippet = CreateSnippetService.new(@project, current_user, create_params = snippet_params.merge(request: request)
snippet_params).execute @snippet = CreateSnippetService.new(@project, current_user, create_params).execute
if @snippet.valid? if @snippet.valid?
respond_with(@snippet, respond_with(@snippet,
...@@ -88,6 +89,7 @@ class Projects::SnippetsController < Projects::ApplicationController ...@@ -88,6 +89,7 @@ class Projects::SnippetsController < Projects::ApplicationController
@snippet ||= @project.snippets.find(params[:id]) @snippet ||= @project.snippets.find(params[:id])
end end
alias_method :awardable, :snippet alias_method :awardable, :snippet
alias_method :spammable, :snippet
def authorize_read_project_snippet! def authorize_read_project_snippet!
return render_404 unless can?(current_user, :read_project_snippet, @snippet) return render_404 unless can?(current_user, :read_project_snippet, @snippet)
......
class SnippetsController < ApplicationController class SnippetsController < ApplicationController
include ToggleAwardEmoji include ToggleAwardEmoji
include SpammableActions
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :download] before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :download]
...@@ -40,8 +41,8 @@ class SnippetsController < ApplicationController ...@@ -40,8 +41,8 @@ class SnippetsController < ApplicationController
end end
def create def create
@snippet = CreateSnippetService.new(nil, current_user, create_params = snippet_params.merge(request: request)
snippet_params).execute @snippet = CreateSnippetService.new(nil, current_user, create_params).execute
respond_with @snippet.becomes(Snippet) respond_with @snippet.becomes(Snippet)
end end
...@@ -96,6 +97,7 @@ class SnippetsController < ApplicationController ...@@ -96,6 +97,7 @@ class SnippetsController < ApplicationController
end end
end end
alias_method :awardable, :snippet alias_method :awardable, :snippet
alias_method :spammable, :snippet
def authorize_read_snippet! def authorize_read_snippet!
authenticate_user! unless can?(current_user, :read_personal_snippet, @snippet) authenticate_user! unless can?(current_user, :read_personal_snippet, @snippet)
......
...@@ -21,7 +21,7 @@ module BlobHelper ...@@ -21,7 +21,7 @@ module BlobHelper
options[:link_opts]) options[:link_opts])
if !on_top_of_branch?(project, ref) if !on_top_of_branch?(project, ref)
button_tag "Edit", class: "btn disabled has-tooltip btn-file-option", title: "You can only edit files when you are on a branch", data: { container: 'body' } button_tag "Edit", class: "btn disabled has-tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' }
elsif can_edit_blob?(blob, project, ref) elsif can_edit_blob?(blob, project, ref)
link_to "Edit", edit_path, class: 'btn btn-sm' link_to "Edit", edit_path, class: 'btn btn-sm'
elsif can?(current_user, :fork_project, project) elsif can?(current_user, :fork_project, project)
...@@ -32,7 +32,7 @@ module BlobHelper ...@@ -32,7 +32,7 @@ module BlobHelper
} }
fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params) fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params)
link_to "Edit", fork_path, class: 'btn btn-file-option', method: :post link_to "Edit", fork_path, class: 'btn', method: :post
end end
end end
......
...@@ -198,7 +198,7 @@ module CommitsHelper ...@@ -198,7 +198,7 @@ module CommitsHelper
link_to( link_to(
namespace_project_blob_path(project.namespace, project, namespace_project_blob_path(project.namespace, project,
tree_join(commit_sha, diff_new_path)), tree_join(commit_sha, diff_new_path)),
class: 'btn view-file js-view-file btn-file-option' class: 'btn view-file js-view-file'
) do ) do
raw('View file @') + content_tag(:span, commit_sha[0..6], raw('View file @') + content_tag(:span, commit_sha[0..6],
class: 'commit-short-id') class: 'commit-short-id')
......
...@@ -93,10 +93,6 @@ module VisibilityLevelHelper ...@@ -93,10 +93,6 @@ module VisibilityLevelHelper
current_application_settings.default_project_visibility current_application_settings.default_project_visibility
end end
def default_snippet_visibility
current_application_settings.default_snippet_visibility
end
def default_group_visibility def default_group_visibility
current_application_settings.default_group_visibility current_application_settings.default_group_visibility
end end
......
...@@ -275,18 +275,13 @@ module Ci ...@@ -275,18 +275,13 @@ module Ci
end end
def update_coverage def update_coverage
return unless project
coverage_regex = project.build_coverage_regex
return unless coverage_regex
coverage = extract_coverage(trace, coverage_regex) coverage = extract_coverage(trace, coverage_regex)
update_attributes(coverage: coverage) if coverage.present?
if coverage.is_a? Numeric
update_attributes(coverage: coverage)
end
end end
def extract_coverage(text, regex) def extract_coverage(text, regex)
begin return unless regex
matches = text.scan(Regexp.new(regex)).last matches = text.scan(Regexp.new(regex)).last
matches = matches.last if matches.kind_of?(Array) matches = matches.last if matches.kind_of?(Array)
coverage = matches.gsub(/\d+(\.\d+)?/).first coverage = matches.gsub(/\d+(\.\d+)?/).first
...@@ -298,7 +293,6 @@ module Ci ...@@ -298,7 +293,6 @@ module Ci
# if bad regex or something goes wrong we dont want to interrupt transition # if bad regex or something goes wrong we dont want to interrupt transition
# so we just silentrly ignore error for now # so we just silentrly ignore error for now
end end
end
def has_trace_file? def has_trace_file?
File.exist?(path_to_trace) || has_old_trace_file? File.exist?(path_to_trace) || has_old_trace_file?
...@@ -522,6 +516,10 @@ module Ci ...@@ -522,6 +516,10 @@ module Ci
self.update(artifacts_expire_at: nil) self.update(artifacts_expire_at: nil)
end end
def coverage_regex
super || project.try(:build_coverage_regex)
end
def when def when
read_attribute(:when) || build_attributes_from_config[:when] || 'on_success' read_attribute(:when) || build_attributes_from_config[:when] || 'on_success'
end end
......
...@@ -34,7 +34,13 @@ module Spammable ...@@ -34,7 +34,13 @@ module Spammable
end end
def check_for_spam def check_for_spam
self.errors.add(:base, "Your #{self.class.name.underscore} has been recognized as spam and has been discarded.") if spam? if spam?
self.errors.add(:base, "Your #{spammable_entity_type} has been recognized as spam and has been discarded.")
end
end
def spammable_entity_type
self.class.name.underscore
end end
def spam_title def spam_title
......
...@@ -31,13 +31,13 @@ class ChatSlashCommandsService < Service ...@@ -31,13 +31,13 @@ class ChatSlashCommandsService < Service
return unless valid_token?(params[:token]) return unless valid_token?(params[:token])
user = find_chat_user(params) user = find_chat_user(params)
unless user
if user
Gitlab::ChatCommands::Command.new(project, user, params).execute
else
url = authorize_chat_name_url(params) url = authorize_chat_name_url(params)
return presenter.authorize_chat_name(url) Gitlab::ChatCommands::Presenters::Access.new(url).authorize
end end
Gitlab::ChatCommands::Command.new(project, user,
params).execute
end end
private private
...@@ -49,8 +49,4 @@ class ChatSlashCommandsService < Service ...@@ -49,8 +49,4 @@ class ChatSlashCommandsService < Service
def authorize_chat_name_url(params) def authorize_chat_name_url(params)
ChatNames::AuthorizeUserService.new(self, params).execute ChatNames::AuthorizeUserService.new(self, params).execute
end end
def presenter
Gitlab::ChatCommands::Presenter.new
end
end end
...@@ -9,4 +9,8 @@ class ProjectSnippet < Snippet ...@@ -9,4 +9,8 @@ class ProjectSnippet < Snippet
participant :author participant :author
participant :notes_with_associations participant :notes_with_associations
def check_for_spam?
super && project.public?
end
end end
...@@ -7,6 +7,7 @@ class Snippet < ActiveRecord::Base ...@@ -7,6 +7,7 @@ class Snippet < ActiveRecord::Base
include Sortable include Sortable
include Awardable include Awardable
include Mentionable include Mentionable
include Spammable
cache_markdown_field :title, pipeline: :single_line cache_markdown_field :title, pipeline: :single_line
cache_markdown_field :content cache_markdown_field :content
...@@ -17,7 +18,7 @@ class Snippet < ActiveRecord::Base ...@@ -17,7 +18,7 @@ class Snippet < ActiveRecord::Base
default_content_html_invalidator || file_name_changed? default_content_html_invalidator || file_name_changed?
end end
default_value_for :visibility_level, Snippet::PRIVATE default_value_for(:visibility_level) { current_application_settings.default_snippet_visibility }
belongs_to :author, class_name: 'User' belongs_to :author, class_name: 'User'
belongs_to :project belongs_to :project
...@@ -46,6 +47,9 @@ class Snippet < ActiveRecord::Base ...@@ -46,6 +47,9 @@ class Snippet < ActiveRecord::Base
participant :author participant :author
participant :notes_with_associations participant :notes_with_associations
attr_spammable :title, spam_title: true
attr_spammable :content, spam_description: true
def self.reference_prefix def self.reference_prefix
'$' '$'
end end
...@@ -127,6 +131,14 @@ class Snippet < ActiveRecord::Base ...@@ -127,6 +131,14 @@ class Snippet < ActiveRecord::Base
notes.includes(:author) notes.includes(:author)
end end
def check_for_spam?
public?
end
def spammable_entity_type
'snippet'
end
class << self class << self
# Searches for snippets with a matching title or file name. # Searches for snippets with a matching title or file name.
# #
......
class CreateSnippetService < BaseService class CreateSnippetService < BaseService
def execute def execute
request = params.delete(:request)
api = params.delete(:api)
snippet = if project snippet = if project
project.snippets.build(params) project.snippets.build(params)
else else
...@@ -12,8 +15,12 @@ class CreateSnippetService < BaseService ...@@ -12,8 +15,12 @@ class CreateSnippetService < BaseService
end end
snippet.author = current_user snippet.author = current_user
snippet.spam = SpamService.new(snippet, request).check(api)
if snippet.save
UserAgentDetailService.new(snippet, request).create
end
snippet.save
snippet snippet
end end
end end
= render 'layouts/nav/admin_settings'
.scrolling-tabs-container{ class: nav_control_class } .scrolling-tabs-container{ class: nav_control_class }
= render 'layouts/nav/admin_settings'
.fade-left .fade-left
= icon('angle-left') = icon('angle-left')
.fade-right .fade-right
......
...@@ -63,9 +63,10 @@ ...@@ -63,9 +63,10 @@
- if @commit.status - if @commit.status
.well-segment.pipeline-info .well-segment.pipeline-info
%div{ class: "icon-container ci-status-icon-#{@commit.status}" } %div{ class: "icon-container ci-status-icon-#{@commit.status}" }
= link_to namespace_project_pipeline_path(@project.namespace, @project, @commit.pipelines.last.id) do
= ci_icon_for_status(@commit.status) = ci_icon_for_status(@commit.status)
Pipeline Pipeline
= link_to "##{@commit.pipelines.last.id}", pipelines_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "monospace" = link_to "##{@commit.pipelines.last.id}", namespace_project_pipeline_path(@project.namespace, @project, @commit.pipelines.last.id), class: "monospace"
for for
= link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace" = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace"
%span.ci-status-label %span.ci-status-label
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
- unless diff_file.submodule? - unless diff_file.submodule?
.file-actions.hidden-xs .file-actions.hidden-xs
- if blob_text_viewable?(blob) - if blob_text_viewable?(blob)
= link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip btn-file-option', title: "Toggle comments for this file", disabled: @diff_notes_disabled do = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip', title: "Toggle comments for this file", disabled: @diff_notes_disabled do
= icon('comment') = icon('comment')
\ \
- if editable_diff?(diff_file) - if editable_diff?(diff_file)
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
%div{ class: container_class } %div{ class: container_class }
.top-area.adjust .top-area.adjust
.col-md-9 .col-md-9
%h3.page-title= @environment.name.capitalize %h3.page-title= @environment.name
.col-md-3 .col-md-3
.nav-controls .nav-controls
= render 'projects/environments/terminal_button', environment: @environment = render 'projects/environments/terminal_button', environment: @environment
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
%th ID %th ID
%th Commit %th Commit
%th Build %th Build
%th %th Created
%th.hidden-xs %th.hidden-xs
= render @deployments = render @deployments
......
...@@ -8,5 +8,5 @@ ...@@ -8,5 +8,5 @@
'@click' => "onClickResolveModeButton(file, 'edit')", '@click' => "onClickResolveModeButton(file, 'edit')",
type: 'button' } type: 'button' }
Edit inline Edit inline
%a.btn.view-file.btn-file-option{ ":href" => "file.blobPath" } %a.btn.view-file{ ":href" => "file.blobPath" }
View file @{{conflictsData.shortCommitSha}} View file @{{conflictsData.shortCommitSha}}
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
.mr-widget-heading .mr-widget-heading
- %w[success success_with_warnings skipped canceled failed running pending].each do |status| - %w[success success_with_warnings skipped canceled failed running pending].each do |status|
.ci_widget{ class: "ci-#{status} ci-status-icon-#{status}", style: ("display:none" unless @pipeline.status == status) } .ci_widget{ class: "ci-#{status} ci-status-icon-#{status}", style: ("display:none" unless @pipeline.status == status) }
= link_to namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'icon-link' do
= ci_icon_for_status(status) = ci_icon_for_status(status)
%span %span
Pipeline Pipeline
...@@ -9,7 +10,7 @@ ...@@ -9,7 +10,7 @@
= ci_label_for_status(status) = ci_label_for_status(status)
for for
= succeed "." do = succeed "." do
= link_to @pipeline.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @pipeline.sha), class: "monospace" = link_to @pipeline.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @pipeline.sha), class: "monospace js-commit-link"
%span.ci-coverage %span.ci-coverage
- elsif @merge_request.has_ci? - elsif @merge_request.has_ci?
......
...@@ -24,6 +24,10 @@ ...@@ -24,6 +24,10 @@
preparing: "{{status}} build", preparing: "{{status}} build",
normal: "Build {{status}}" normal: "Build {{status}}"
}, },
ci_sha: "#{@merge_request.head_pipeline ? @merge_request.head_pipeline.short_sha : ''}",
ci_pipeline: #{@merge_request.head_pipeline.try(:id).to_json},
commits_path: "#{project_commits_path(@project)}",
pipeline_path: "#{project_pipelines_path(@project)}",
pipelines_path: "#{pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}" pipelines_path: "#{pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}"
}; };
......
...@@ -23,8 +23,8 @@ ...@@ -23,8 +23,8 @@
.info-well .info-well
- if @commit.status - if @commit.status
.well-segment.pipeline-info .well-segment.pipeline-info
%div{ class: "icon-container ci-status-icon-#{@commit.status}" } .icon-container
= ci_icon_for_status(@commit.status) = icon('clock-o')
= pluralize @pipeline.statuses.count(:id), "build" = pluralize @pipeline.statuses.count(:id), "build"
- if @pipeline.ref - if @pipeline.ref
from from
......
...@@ -8,6 +8,8 @@ ...@@ -8,6 +8,8 @@
- if can?(current_user, :create_project_snippet, @project) - if can?(current_user, :create_project_snippet, @project)
= link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-inverted btn-create', title: "New snippet" do = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-inverted btn-create', title: "New snippet" do
New snippet New snippet
- if @snippet.submittable_as_spam? && current_user.admin?
= link_to 'Submit as spam', mark_as_spam_namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam'
- if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet) - if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet)
.visible-xs-block.dropdown .visible-xs-block.dropdown
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
...@@ -27,3 +29,6 @@ ...@@ -27,3 +29,6 @@
%li %li
= link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do
Edit Edit
- if @snippet.submittable_as_spam? && current_user.admin?
%li
= link_to 'Submit as spam', mark_as_spam_namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :post
...@@ -3,4 +3,4 @@ ...@@ -3,4 +3,4 @@
%h3.page-title %h3.page-title
Edit Snippet Edit Snippet
%hr %hr
= render "shared/snippets/form", url: namespace_project_snippet_path(@project.namespace, @project, @snippet), visibility_level: @snippet.visibility_level = render "shared/snippets/form", url: namespace_project_snippet_path(@project.namespace, @project, @snippet)
...@@ -3,4 +3,4 @@ ...@@ -3,4 +3,4 @@
%h3.page-title %h3.page-title
New Snippet New Snippet
%hr %hr
= render "shared/snippets/form", url: namespace_project_snippets_path(@project.namespace, @project, @snippet), visibility_level: default_snippet_visibility = render "shared/snippets/form", url: namespace_project_snippets_path(@project.namespace, @project, @snippet)
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
.col-sm-10 .col-sm-10
= f.text_field :title, class: 'form-control', required: true, autofocus: true = f.text_field :title, class: 'form-control', required: true, autofocus: true
= render 'shared/visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: true, form_model: @snippet = render 'shared/visibility_level', f: f, visibility_level: @snippet.visibility_level, can_change_visibility_level: true, form_model: @snippet
.file-editor .file-editor
.form-group .form-group
......
...@@ -8,6 +8,8 @@ ...@@ -8,6 +8,8 @@
- if current_user - if current_user
= link_to new_snippet_path, class: "btn btn-grouped btn-inverted btn-create", title: "New snippet" do = link_to new_snippet_path, class: "btn btn-grouped btn-inverted btn-create", title: "New snippet" do
New snippet New snippet
- if @snippet.submittable_as_spam? && current_user.admin?
= link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam'
- if current_user - if current_user
.visible-xs-block.dropdown .visible-xs-block.dropdown
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
...@@ -26,3 +28,6 @@ ...@@ -26,3 +28,6 @@
%li %li
= link_to edit_snippet_path(@snippet) do = link_to edit_snippet_path(@snippet) do
Edit Edit
- if @snippet.submittable_as_spam? && current_user.admin?
%li
= link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post
...@@ -2,4 +2,4 @@ ...@@ -2,4 +2,4 @@
%h3.page-title %h3.page-title
Edit Snippet Edit Snippet
%hr %hr
= render 'shared/snippets/form', url: snippet_path(@snippet), visibility_level: @snippet.visibility_level = render 'shared/snippets/form', url: snippet_path(@snippet)
...@@ -2,4 +2,4 @@ ...@@ -2,4 +2,4 @@
%h3.page-title %h3.page-title
New Snippet New Snippet
%hr %hr
= render "shared/snippets/form", url: snippets_path(@snippet), visibility_level: default_snippet_visibility = render "shared/snippets/form", url: snippets_path(@snippet)
--- ---
title: Fix autocomplete initial undefined state title: 19164 Add settings dropdown to mobile screens
merge_request: merge_request:
author: author:
---
title: Reduce hits to LDAP on Git HTTP auth by reordering auth mechanisms
merge_request: 8752
author:
---
title: Update pipeline and commit links when CI status is updated
merge_request: 8351
author:
---
title: Add caching of droplab ajax requests
merge_request: 8725
author:
--- ---
title: Fix race conditions for AuthorizedProjectsWorker title: Improve pipeline status icon linking in widgets
merge_request: merge_request:
author: author:
---
title: Support non-ASCII characters in GFM autocomplete
merge_request: 8729
author:
--- ---
title: Fixed label dropdown toggle text not correctly updating title: Fix permalink discussion note being collapsed
merge_request: merge_request:
author: author:
---
title: Unify MR diff file button style
merge_request: 8874
author:
---
title: Don't capitalize environment name in show page
merge_request:
author:
---
title: Edited the column header for the environments list from created to updated and added created to environments detail page colum header titles
merge_request:
author:
---
title: Change the reply shortcut to focus the field even without a selection.
merge_request: 8873
author: Brian Hall
---
title: Fix access to the wiki code via HTTP when repository feature disabled
merge_request: 8758
author:
---
title: resolve deprecation warnings
merge_request: 8855
author: Adam Pahlevi
---
title: Fix filtering usernames with multiple words
merge_request: 8851
author:
---
title: Remove old project members when retrying an export
merge_request:
author:
---
title: Add ability to define a coverage regex in the .gitlab-ci.yml
merge_request: 7447
author: Leandro Camargo
---
title: Revert 3f17f29a
merge_request: 8785
author:
---
title: Fix Error 500 when repositories contain annotated tags pointing to blobs
merge_request:
author:
--- ---
title: Fix /explore sorting title: Check public snippets for spam
merge_request: merge_request:
author: author:
---
title: Reformat messages ChatOps
merge_request: 8528
author:
...@@ -64,6 +64,7 @@ constraints(ProjectUrlConstrainer.new) do ...@@ -64,6 +64,7 @@ constraints(ProjectUrlConstrainer.new) do
resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do
member do member do
get 'raw' get 'raw'
post :mark_as_spam
end end
end end
......
...@@ -2,6 +2,7 @@ resources :snippets, concerns: :awardable do ...@@ -2,6 +2,7 @@ resources :snippets, concerns: :awardable do
member do member do
get 'raw' get 'raw'
get 'download' get 'download'
post :mark_as_spam
end end
end end
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddCoverageRegexToBuilds < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def change
add_column :ci_builds, :coverage_regex, :string
end
end
...@@ -215,6 +215,7 @@ ActiveRecord::Schema.define(version: 20170130204620) do ...@@ -215,6 +215,7 @@ ActiveRecord::Schema.define(version: 20170130204620) do
t.datetime "queued_at" t.datetime "queued_at"
t.string "token" t.string "token"
t.integer "lock_version" t.integer "lock_version"
t.string "coverage_regex"
end end
add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree
......
...@@ -76,6 +76,7 @@ There are a few reserved `keywords` that **cannot** be used as job names: ...@@ -76,6 +76,7 @@ There are a few reserved `keywords` that **cannot** be used as job names:
| after_script | no | Define commands that run after each job's script | | after_script | no | Define commands that run after each job's script |
| variables | no | Define build variables | | variables | no | Define build variables |
| cache | no | Define list of files that should be cached between subsequent runs | | cache | no | Define list of files that should be cached between subsequent runs |
| coverage | no | Define coverage settings for all jobs |
### image and services ### image and services
...@@ -278,6 +279,23 @@ cache: ...@@ -278,6 +279,23 @@ cache:
untracked: true untracked: true
``` ```
### coverage
`coverage` allows you to configure how coverage will be filtered out from the
build outputs. Setting this up globally will make all the jobs to use this
setting for output filtering and extracting the coverage information from your
builds.
Regular expressions are the only valid kind of value expected here. So, using
surrounding `/` is mandatory in order to consistently and explicitly represent
a regular expression string. You must escape special characters if you want to
match them literally.
A simple example:
```yaml
coverage: /\(\d+\.\d+\) covered\./
```
## Jobs ## Jobs
`.gitlab-ci.yml` allows you to specify an unlimited number of jobs. Each job `.gitlab-ci.yml` allows you to specify an unlimited number of jobs. Each job
...@@ -319,6 +337,7 @@ job_name: ...@@ -319,6 +337,7 @@ job_name:
| before_script | no | Override a set of commands that are executed before build | | before_script | no | Override a set of commands that are executed before build |
| after_script | no | Override a set of commands that are executed after build | | after_script | no | Override a set of commands that are executed after build |
| environment | no | Defines a name of environment to which deployment is done by this build | | environment | no | Defines a name of environment to which deployment is done by this build |
| coverage | no | Define coverage settings for a given job |
### script ### script
...@@ -993,6 +1012,25 @@ job: ...@@ -993,6 +1012,25 @@ job:
- execute this after my script - execute this after my script
``` ```
### job coverage
This entry is pretty much the same as described in the global context in
[`coverage`](#coverage). The only difference is that, by setting it inside
the job level, whatever is set in there will take precedence over what has
been defined in the global level. A quick example of one overriding the
other would be:
```yaml
coverage: /\(\d+\.\d+\) covered\./
job1:
coverage: /Code coverage: \d+\.\d+/
```
In the example above, considering the context of the job `job1`, the coverage
regex that would be used is `/Code coverage: \d+\.\d+/` instead of
`/\(\d+\.\d+\) covered\./`.
## Git Strategy ## Git Strategy
> Introduced in GitLab 8.9 as an experimental feature. May change or be removed > Introduced in GitLab 8.9 as an experimental feature. May change or be removed
......
...@@ -19,7 +19,7 @@ Easing specifies the rate of change of a parameter over time (see [easings.net]( ...@@ -19,7 +19,7 @@ Easing specifies the rate of change of a parameter over time (see [easings.net](
### Hover ### Hover
Interactive elements (links, buttons, etc.) should have a hover state. A subtle animation for this transition adds a polished feel. We should target a `200ms linear` transition for a color hover effect. Interactive elements (links, buttons, etc.) should have a hover state. A subtle animation for this transition adds a polished feel. We should target a `100ms - 150ms linear` transition for a color hover effect.
View the [interactive example](http://codepen.io/awhildy/full/GNyEvM/) here. View the [interactive example](http://codepen.io/awhildy/full/GNyEvM/) here.
......
@dashboard
Feature: Dashboard Shortcuts
Background:
Given I sign in as a user
And I visit dashboard page
@javascript
Scenario: Navigate to projects tab
Given I press "g" and "p"
Then the active main tab should be Projects
@javascript
Scenario: Navigate to issue tab
Given I press "g" and "i"
Then the active main tab should be Issues
@javascript
Scenario: Navigate to merge requests tab
Given I press "g" and "m"
Then the active main tab should be Merge Requests
class Spinach::Features::DashboardShortcuts < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedProject
include SharedSidebarActiveTab
include SharedShortcuts
end
...@@ -58,7 +58,7 @@ module API ...@@ -58,7 +58,7 @@ module API
end end
post ":id/snippets" do post ":id/snippets" do
authorize! :create_project_snippet, user_project authorize! :create_project_snippet, user_project
snippet_params = declared_params snippet_params = declared_params.merge(request: request, api: true)
snippet_params[:content] = snippet_params.delete(:code) snippet_params[:content] = snippet_params.delete(:code)
snippet = CreateSnippetService.new(user_project, current_user, snippet_params).execute snippet = CreateSnippetService.new(user_project, current_user, snippet_params).execute
......
...@@ -64,7 +64,7 @@ module API ...@@ -64,7 +64,7 @@ module API
desc: 'The visibility level of the snippet' desc: 'The visibility level of the snippet'
end end
post do post do
attrs = declared_params(include_missing: false) attrs = declared_params(include_missing: false).merge(request: request, api: true)
snippet = CreateSnippetService.new(nil, current_user, attrs).execute snippet = CreateSnippetService.new(nil, current_user, attrs).execute
if snippet.persisted? if snippet.persisted?
......
...@@ -61,6 +61,7 @@ module Ci ...@@ -61,6 +61,7 @@ module Ci
allow_failure: job[:allow_failure] || false, allow_failure: job[:allow_failure] || false,
when: job[:when] || 'on_success', when: job[:when] || 'on_success',
environment: job[:environment_name], environment: job[:environment_name],
coverage_regex: job[:coverage],
yaml_variables: yaml_variables(name), yaml_variables: yaml_variables(name),
options: { options: {
image: job[:image], image: job[:image],
......
...@@ -10,13 +10,16 @@ module Gitlab ...@@ -10,13 +10,16 @@ module Gitlab
def find_for_git_client(login, password, project:, ip:) def find_for_git_client(login, password, project:, ip:)
raise "Must provide an IP for rate limiting" if ip.nil? raise "Must provide an IP for rate limiting" if ip.nil?
# `user_with_password_for_git` should be the last check
# because it's the most expensive, especially when LDAP
# is enabled.
result = result =
service_request_check(login, password, project) || service_request_check(login, password, project) ||
build_access_token_check(login, password) || build_access_token_check(login, password) ||
user_with_password_for_git(login, password) ||
oauth_access_token_check(login, password) ||
lfs_token_check(login, password) || lfs_token_check(login, password) ||
oauth_access_token_check(login, password) ||
personal_access_token_check(login, password) || personal_access_token_check(login, password) ||
user_with_password_for_git(login, password) ||
Gitlab::Auth::Result.new Gitlab::Auth::Result.new
rate_limit!(ip, success: result.success?, login: login) rate_limit!(ip, success: result.success?, login: login)
...@@ -143,7 +146,9 @@ module Gitlab ...@@ -143,7 +146,9 @@ module Gitlab
read_authentication_abilities read_authentication_abilities
end end
Result.new(actor, nil, token_handler.type, authentication_abilities) if Devise.secure_compare(token_handler.token, password) if Devise.secure_compare(token_handler.token, password)
Gitlab::Auth::Result.new(actor, nil, token_handler.type, authentication_abilities)
end
end end
def build_access_token_check(login, password) def build_access_token_check(login, password)
......
...@@ -42,10 +42,6 @@ module Gitlab ...@@ -42,10 +42,6 @@ module Gitlab
def find_by_iid(iid) def find_by_iid(iid)
collection.find_by(iid: iid) collection.find_by(iid: iid)
end end
def presenter
Gitlab::ChatCommands::Presenter.new
end
end end
end end
end end
...@@ -3,7 +3,7 @@ module Gitlab ...@@ -3,7 +3,7 @@ module Gitlab
class Command < BaseCommand class Command < BaseCommand
COMMANDS = [ COMMANDS = [
Gitlab::ChatCommands::IssueShow, Gitlab::ChatCommands::IssueShow,
Gitlab::ChatCommands::IssueCreate, Gitlab::ChatCommands::IssueNew,
Gitlab::ChatCommands::IssueSearch, Gitlab::ChatCommands::IssueSearch,
Gitlab::ChatCommands::Deploy, Gitlab::ChatCommands::Deploy,
].freeze ].freeze
...@@ -13,19 +13,20 @@ module Gitlab ...@@ -13,19 +13,20 @@ module Gitlab
if command if command
if command.allowed?(project, current_user) if command.allowed?(project, current_user)
present command.new(project, current_user, params).execute(match) command.new(project, current_user, params).execute(match)
else else
access_denied Gitlab::ChatCommands::Presenters::Access.new.access_denied
end end
else else
help(help_messages) Gitlab::ChatCommands::Help.new(project, current_user, params).execute(available_commands, params[:text])
end end
end end
def match_command def match_command
match = nil match = nil
service = available_commands.find do |klass| service =
match = klass.match(command) available_commands.find do |klass|
match = klass.match(params[:text])
end end
[service, match] [service, match]
...@@ -33,31 +34,11 @@ module Gitlab ...@@ -33,31 +34,11 @@ module Gitlab
private private
def help_messages
available_commands.map(&:help_message)
end
def available_commands def available_commands
COMMANDS.select do |klass| COMMANDS.select do |klass|
klass.available?(project) klass.available?(project)
end end
end end
def command
params[:text]
end
def help(messages)
presenter.help(messages, params[:command])
end
def access_denied
presenter.access_denied
end
def present(resource)
presenter.present(resource)
end
end end
end end
end end
module Gitlab module Gitlab
module ChatCommands module ChatCommands
class Deploy < BaseCommand class Deploy < BaseCommand
include Gitlab::Routing.url_helpers
def self.match(text) def self.match(text)
/\Adeploy\s+(?<from>\S+.*)\s+to+\s+(?<to>\S+.*)\z/.match(text) /\Adeploy\s+(?<from>\S+.*)\s+to+\s+(?<to>\S+.*)\z/.match(text)
end end
...@@ -24,35 +22,29 @@ module Gitlab ...@@ -24,35 +22,29 @@ module Gitlab
to = match[:to] to = match[:to]
actions = find_actions(from, to) actions = find_actions(from, to)
return unless actions.present?
if actions.one? if actions.none?
play!(from, to, actions.first) Gitlab::ChatCommands::Presenters::Deploy.new(nil).no_actions
elsif actions.one?
action = play!(from, to, actions.first)
Gitlab::ChatCommands::Presenters::Deploy.new(action).present(from, to)
else else
Result.new(:error, 'Too many actions defined') Gitlab::ChatCommands::Presenters::Deploy.new(actions).too_many_actions
end end
end end
private private
def play!(from, to, action) def play!(from, to, action)
new_action = action.play(current_user) action.play(current_user)
Result.new(:success, "Deployment from #{from} to #{to} started. Follow the progress: #{url(new_action)}.")
end end
def find_actions(from, to) def find_actions(from, to)
environment = project.environments.find_by(name: from) environment = project.environments.find_by(name: from)
return unless environment return [] unless environment
environment.actions_for(to).select(&:starts_environment?) environment.actions_for(to).select(&:starts_environment?)
end end
def url(subject)
polymorphic_url(
[subject.project.namespace.becomes(Namespace), subject.project, subject]
)
end
end end
end end
end end
module Gitlab
module ChatCommands
class Help < BaseCommand
# This class has to be used last, as it always matches. It has to match
# because other commands were not triggered and we want to show the help
# command
def self.match(_text)
true
end
def self.help_message
'help'
end
def self.allowed?(_project, _user)
true
end
def execute(commands, text)
Gitlab::ChatCommands::Presenters::Help.new(commands).present(trigger, text)
end
def trigger
params[:command]
end
end
end
end
module Gitlab module Gitlab
module ChatCommands module ChatCommands
class IssueCreate < IssueCommand class IssueNew < IssueCommand
def self.match(text) def self.match(text)
# we can not match \n with the dot by passing the m modifier as than # we can not match \n with the dot by passing the m modifier as than
# the title and description are not seperated # the title and description are not seperated
...@@ -19,8 +19,24 @@ module Gitlab ...@@ -19,8 +19,24 @@ module Gitlab
title = match[:title] title = match[:title]
description = match[:description].to_s.rstrip description = match[:description].to_s.rstrip
issue = create_issue(title: title, description: description)
if issue.persisted?
presenter(issue).present
else
presenter(issue).display_errors
end
end
private
def create_issue(title:, description:)
Issues::CreateService.new(project, current_user, title: title, description: description).execute Issues::CreateService.new(project, current_user, title: title, description: description).execute
end end
def presenter(issue)
Gitlab::ChatCommands::Presenters::IssueNew.new(issue)
end
end end
end end
end end
...@@ -10,7 +10,13 @@ module Gitlab ...@@ -10,7 +10,13 @@ module Gitlab
end end
def execute(match) def execute(match)
collection.search(match[:query]).limit(QUERY_LIMIT) issues = collection.search(match[:query]).limit(QUERY_LIMIT)
if issues.present?
Presenters::IssueSearch.new(issues).present
else
Presenters::Access.new(issues).not_found
end
end end
end end
end end
......
...@@ -10,7 +10,13 @@ module Gitlab ...@@ -10,7 +10,13 @@ module Gitlab
end end
def execute(match) def execute(match)
find_by_iid(match[:iid]) issue = find_by_iid(match[:iid])
if issue
Gitlab::ChatCommands::Presenters::IssueShow.new(issue).present
else
Gitlab::ChatCommands::Presenters::Access.new.not_found
end
end end
end end
end end
......
module Gitlab
module ChatCommands
class Presenter
include Gitlab::Routing
def authorize_chat_name(url)
message = if url
":wave: Hi there! Before I do anything for you, please [connect your GitLab account](#{url})."
else
":sweat_smile: Couldn't identify you, nor can I autorize you!"
end
ephemeral_response(message)
end
def help(commands, trigger)
if commands.none?
ephemeral_response("No commands configured")
else
commands.map! { |command| "#{trigger} #{command}" }
message = header_with_list("Available commands", commands)
ephemeral_response(message)
end
end
def present(subject)
return not_found unless subject
if subject.is_a?(Gitlab::ChatCommands::Result)
show_result(subject)
elsif subject.respond_to?(:count)
if subject.none?
not_found
elsif subject.one?
single_resource(subject.first)
else
multiple_resources(subject)
end
else
single_resource(subject)
end
end
def access_denied
ephemeral_response("Whoops! That action is not allowed. This incident will be [reported](https://xkcd.com/838/).")
end
private
def show_result(result)
case result.type
when :success
in_channel_response(result.message)
else
ephemeral_response(result.message)
end
end
def not_found
ephemeral_response("404 not found! GitLab couldn't find what you were looking for! :boom:")
end
def single_resource(resource)
return error(resource) if resource.errors.any? || !resource.persisted?
message = "#{title(resource)}:"
message << "\n\n#{resource.description}" if resource.try(:description)
in_channel_response(message)
end
def multiple_resources(resources)
titles = resources.map { |resource| title(resource) }
message = header_with_list("Multiple results were found:", titles)
ephemeral_response(message)
end
def error(resource)
message = header_with_list("The action was not successful, because:", resource.errors.messages)
ephemeral_response(message)
end
def title(resource)
reference = resource.try(:to_reference) || resource.try(:id)
title = resource.try(:title) || resource.try(:name)
"[#{reference} #{title}](#{url(resource)})"
end
def header_with_list(header, items)
message = [header]
items.each do |item|
message << "- #{item}"
end
message.join("\n")
end
def url(resource)
url_for(
[
resource.project.namespace.becomes(Namespace),
resource.project,
resource
]
)
end
def ephemeral_response(message)
{
response_type: :ephemeral,
text: message,
status: 200
}
end
def in_channel_response(message)
{
response_type: :in_channel,
text: message,
status: 200
}
end
end
end
end
module Gitlab
module ChatCommands
module Presenters
class Access < Presenters::Base
def access_denied
ephemeral_response(text: "Whoops! This action is not allowed. This incident will be [reported](https://xkcd.com/838/).")
end
def not_found
ephemeral_response(text: "404 not found! GitLab couldn't find what you were looking for! :boom:")
end
def authorize
message =
if @resource
":wave: Hi there! Before I do anything for you, please [connect your GitLab account](#{@resource})."
else
":sweat_smile: Couldn't identify you, nor can I autorize you!"
end
ephemeral_response(text: message)
end
def unknown_command(commands)
ephemeral_response(text: help_message(trigger))
end
private
def help_message(trigger)
header_with_list("Command not found, these are the commands you can use", full_commands(trigger))
end
def full_commands(trigger)
@resource.map { |command| "#{trigger} #{command.help_message}" }
end
end
end
end
end
module Gitlab
module ChatCommands
module Presenters
class Base
include Gitlab::Routing.url_helpers
def initialize(resource = nil)
@resource = resource
end
def display_errors
message = header_with_list("The action was not successful, because:", @resource.errors.full_messages)
ephemeral_response(text: message)
end
private
def header_with_list(header, items)
message = [header]
items.each do |item|
message << "- #{item}"
end
message.join("\n")
end
def ephemeral_response(message)
response = {
response_type: :ephemeral,
status: 200
}.merge(message)
format_response(response)
end
def in_channel_response(message)
response = {
response_type: :in_channel,
status: 200
}.merge(message)
format_response(response)
end
def format_response(response)
response[:text] = format(response[:text]) if response.has_key?(:text)
if response.has_key?(:attachments)
response[:attachments].each do |attachment|
attachment[:pretext] = format(attachment[:pretext]) if attachment[:pretext]
attachment[:text] = format(attachment[:text]) if attachment[:text]
end
end
response
end
# Convert Markdown to slacks format
def format(string)
Slack::Notifier::LinkFormatter.format(string)
end
def resource_url
url_for(
[
@resource.project.namespace.becomes(Namespace),
@resource.project,
@resource
]
)
end
end
end
end
end
module Gitlab
module ChatCommands
module Presenters
class Deploy < Presenters::Base
def present(from, to)
message = "Deployment started from #{from} to #{to}. [Follow its progress](#{resource_url})."
in_channel_response(text: message)
end
def no_actions
ephemeral_response(text: "No action found to be executed")
end
def too_many_actions
ephemeral_response(text: "Too many actions defined")
end
end
end
end
end
module Gitlab
module ChatCommands
module Presenters
class Help < Presenters::Base
def present(trigger, text)
ephemeral_response(text: help_message(trigger, text))
end
private
def help_message(trigger, text)
return "No commands available :thinking_face:" unless @resource.present?
if text.start_with?('help')
header_with_list("Available commands", full_commands(trigger))
else
header_with_list("Unknown command, these commands are available", full_commands(trigger))
end
end
def full_commands(trigger)
@resource.map { |command| "#{trigger} #{command.help_message}" }
end
end
end
end
end
module Gitlab
module ChatCommands
module Presenters
module Issuable
def color(issuable)
issuable.open? ? '#38ae67' : '#d22852'
end
def status_text(issuable)
issuable.open? ? 'Open' : 'Closed'
end
def project
@resource.project
end
def author
@resource.author
end
def fields
[
{
title: "Assignee",
value: @resource.assignee ? @resource.assignee.name : "_None_",
short: true
},
{
title: "Milestone",
value: @resource.milestone ? @resource.milestone.title : "_None_",
short: true
},
{
title: "Labels",
value: @resource.labels.any? ? @resource.label_names : "_None_",
short: true
}
]
end
end
end
end
end
module Gitlab
module ChatCommands
module Presenters
class IssueNew < Presenters::Base
include Presenters::Issuable
def present
in_channel_response(new_issue)
end
private
def new_issue
{
attachments: [
{
title: "#{@resource.title} · #{@resource.to_reference}",
title_link: resource_url,
author_name: author.name,
author_icon: author.avatar_url,
fallback: "New issue #{@resource.to_reference}: #{@resource.title}",
pretext: pretext,
color: color(@resource),
fields: fields,
mrkdwn_in: [
:title,
:pretext,
:text,
:fields
]
}
]
}
end
def pretext
"I created an issue on #{author_profile_link}'s behalf: **#{@resource.to_reference}** in #{project_link}"
end
def project_link
"[#{project.name_with_namespace}](#{projects_url(project)})"
end
def author_profile_link
"[#{author.to_reference}](#{url_for(author)})"
end
end
end
end
end
module Gitlab
module ChatCommands
module Presenters
class IssueSearch < Presenters::Base
include Presenters::Issuable
def present
text = if @resource.count >= 5
"Here are the first 5 issues I found:"
elsif @resource.one?
"Here is the only issue I found:"
else
"Here are the #{@resource.count} issues I found:"
end
ephemeral_response(text: text, attachments: attachments)
end
private
def attachments
@resource.map do |issue|
url = "[#{issue.to_reference}](#{url_for([namespace, project, issue])})"
{
color: color(issue),
fallback: "#{issue.to_reference} #{issue.title}",
text: "#{url} · #{issue.title} (#{status_text(issue)})",
mrkdwn_in: [
:text
]
}
end
end
def project
@project ||= @resource.first.project
end
def namespace
@namespace ||= project.namespace.becomes(Namespace)
end
end
end
end
end
module Gitlab
module ChatCommands
module Presenters
class IssueShow < Presenters::Base
include Presenters::Issuable
def present
if @resource.confidential?
ephemeral_response(show_issue)
else
in_channel_response(show_issue)
end
end
private
def show_issue
{
attachments: [
{
title: "#{@resource.title} · #{@resource.to_reference}",
title_link: resource_url,
author_name: author.name,
author_icon: author.avatar_url,
fallback: "Issue #{@resource.to_reference}: #{@resource.title}",
pretext: pretext,
text: text,
color: color(@resource),
fields: fields,
mrkdwn_in: [
:pretext,
:text,
:fields
]
}
]
}
end
def text
message = "**#{status_text(@resource)}**"
if @resource.upvotes.zero? && @resource.downvotes.zero? && @resource.user_notes_count.zero?
return message
end
message << " · "
message << ":+1: #{@resource.upvotes} " unless @resource.upvotes.zero?
message << ":-1: #{@resource.downvotes} " unless @resource.downvotes.zero?
message << ":speech_balloon: #{@resource.user_notes_count}" unless @resource.user_notes_count.zero?
message
end
def pretext
"Issue *#{@resource.to_reference}* from #{project.name_with_namespace}"
end
end
end
end
end
module Gitlab
module Ci
class Config
module Entry
##
# Entry that represents Coverage settings.
#
class Coverage < Node
include Validatable
validations do
validates :config, regexp: true
end
def value
@config[1...-1]
end
end
end
end
end
end
...@@ -33,8 +33,11 @@ module Gitlab ...@@ -33,8 +33,11 @@ module Gitlab
entry :cache, Entry::Cache, entry :cache, Entry::Cache,
description: 'Configure caching between build jobs.' description: 'Configure caching between build jobs.'
entry :coverage, Entry::Coverage,
description: 'Coverage configuration for this pipeline.'
helpers :before_script, :image, :services, :after_script, helpers :before_script, :image, :services, :after_script,
:variables, :stages, :types, :cache, :jobs :variables, :stages, :types, :cache, :coverage, :jobs
def compose!(_deps = nil) def compose!(_deps = nil)
super(self) do super(self) do
......
...@@ -11,7 +11,7 @@ module Gitlab ...@@ -11,7 +11,7 @@ module Gitlab
ALLOWED_KEYS = %i[tags script only except type image services allow_failure ALLOWED_KEYS = %i[tags script only except type image services allow_failure
type stage when artifacts cache dependencies before_script type stage when artifacts cache dependencies before_script
after_script variables environment] after_script variables environment coverage]
validations do validations do
validates :config, allowed_keys: ALLOWED_KEYS validates :config, allowed_keys: ALLOWED_KEYS
...@@ -71,9 +71,12 @@ module Gitlab ...@@ -71,9 +71,12 @@ module Gitlab
entry :environment, Entry::Environment, entry :environment, Entry::Environment,
description: 'Environment configuration for this job.' description: 'Environment configuration for this job.'
entry :coverage, Entry::Coverage,
description: 'Coverage configuration for this job.'
helpers :before_script, :script, :stage, :type, :after_script, helpers :before_script, :script, :stage, :type, :after_script,
:cache, :image, :services, :only, :except, :variables, :cache, :image, :services, :only, :except, :variables,
:artifacts, :commands, :environment :artifacts, :commands, :environment, :coverage
attributes :script, :tags, :allow_failure, :when, :dependencies attributes :script, :tags, :allow_failure, :when, :dependencies
...@@ -130,6 +133,7 @@ module Gitlab ...@@ -130,6 +133,7 @@ module Gitlab
variables: variables_defined? ? variables_value : nil, variables: variables_defined? ? variables_value : nil,
environment: environment_defined? ? environment_value : nil, environment: environment_defined? ? environment_value : nil,
environment_name: environment_defined? ? environment_value[:name] : nil, environment_name: environment_defined? ? environment_value[:name] : nil,
coverage: coverage_defined? ? coverage_value : nil,
artifacts: artifacts_value, artifacts: artifacts_value,
after_script: after_script_value } after_script: after_script_value }
end end
......
...@@ -28,17 +28,21 @@ module Gitlab ...@@ -28,17 +28,21 @@ module Gitlab
value.is_a?(String) || value.is_a?(Symbol) value.is_a?(String) || value.is_a?(Symbol)
end end
def validate_regexp(value)
!value.nil? && Regexp.new(value.to_s) && true
rescue RegexpError, TypeError
false
end
def validate_string_or_regexp(value) def validate_string_or_regexp(value)
return true if value.is_a?(Symbol) return true if value.is_a?(Symbol)
return false unless value.is_a?(String) return false unless value.is_a?(String)
if value.first == '/' && value.last == '/' if value.first == '/' && value.last == '/'
Regexp.new(value[1...-1]) validate_regexp(value[1...-1])
else else
true true
end end
rescue RegexpError
false
end end
def validate_boolean(value) def validate_boolean(value)
......
...@@ -9,15 +9,7 @@ module Gitlab ...@@ -9,15 +9,7 @@ module Gitlab
include Validatable include Validatable
validations do validations do
include LegacyValidationHelpers validates :config, array_of_strings_or_regexps: true
validate :array_of_strings_or_regexps
def array_of_strings_or_regexps
unless validate_array_of_strings_or_regexps(config)
errors.add(:config, 'should be an array of strings or regexps')
end
end
end end
end end
end end
......
...@@ -54,6 +54,51 @@ module Gitlab ...@@ -54,6 +54,51 @@ module Gitlab
end end
end end
class RegexpValidator < ActiveModel::EachValidator
include LegacyValidationHelpers
def validate_each(record, attribute, value)
unless validate_regexp(value)
record.errors.add(attribute, 'must be a regular expression')
end
end
private
def look_like_regexp?(value)
value.is_a?(String) && value.start_with?('/') &&
value.end_with?('/')
end
def validate_regexp(value)
look_like_regexp?(value) &&
Regexp.new(value.to_s[1...-1]) &&
true
rescue RegexpError
false
end
end
class ArrayOfStringsOrRegexpsValidator < RegexpValidator
def validate_each(record, attribute, value)
unless validate_array_of_strings_or_regexps(value)
record.errors.add(attribute, 'should be an array of strings or regexps')
end
end
private
def validate_array_of_strings_or_regexps(values)
values.is_a?(Array) && values.all?(&method(:validate_string_or_regexp))
end
def validate_string_or_regexp(value)
return false unless value.is_a?(String)
return validate_regexp(value) if look_like_regexp?(value)
true
end
end
class TypeValidator < ActiveModel::EachValidator class TypeValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value) def validate_each(record, attribute, value)
type = options[:with] type = options[:with]
......
...@@ -41,6 +41,8 @@ module Gitlab ...@@ -41,6 +41,8 @@ module Gitlab
end end
def ensure_default_member! def ensure_default_member!
@project.project_members.destroy_all
ProjectMember.create!(user: @user, access_level: ProjectMember::MASTER, source_id: @project.id, importing: true) ProjectMember.create!(user: @user, access_level: ProjectMember::MASTER, source_id: @project.id, importing: true)
end end
......
...@@ -6,8 +6,8 @@ describe Projects::SnippetsController do ...@@ -6,8 +6,8 @@ describe Projects::SnippetsController do
let(:user2) { create(:user) } let(:user2) { create(:user) }
before do before do
project.team << [user, :master] project.add_master(user)
project.team << [user2, :master] project.add_master(user2)
end end
describe 'GET #index' do describe 'GET #index' do
...@@ -69,6 +69,86 @@ describe Projects::SnippetsController do ...@@ -69,6 +69,86 @@ describe Projects::SnippetsController do
end end
end end
describe 'POST #create' do
def create_snippet(project, snippet_params = {})
sign_in(user)
project.add_developer(user)
post :create, {
namespace_id: project.namespace.to_param,
project_id: project.to_param,
project_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params)
}
end
context 'when the snippet is spam' do
before do
allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
end
context 'when the project is private' do
let(:private_project) { create(:project_empty_repo, :private) }
context 'when the snippet is public' do
it 'creates the snippet' do
expect { create_snippet(private_project, visibility_level: Snippet::PUBLIC) }.
to change { Snippet.count }.by(1)
end
end
end
context 'when the project is public' do
context 'when the snippet is private' do
it 'creates the snippet' do
expect { create_snippet(project, visibility_level: Snippet::PRIVATE) }.
to change { Snippet.count }.by(1)
end
end
context 'when the snippet is public' do
it 'rejects the shippet' do
expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
not_to change { Snippet.count }
expect(response).to render_template(:new)
end
it 'creates a spam log' do
expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
to change { SpamLog.count }.by(1)
end
end
end
end
end
describe 'POST #mark_as_spam' do
let(:snippet) { create(:project_snippet, :private, project: project, author: user) }
before do
allow_any_instance_of(AkismetService).to receive_messages(submit_spam: true)
stub_application_setting(akismet_enabled: true)
end
def mark_as_spam
admin = create(:admin)
create(:user_agent_detail, subject: snippet)
project.add_master(admin)
sign_in(admin)
post :mark_as_spam,
namespace_id: project.namespace.path,
project_id: project.path,
id: snippet.id
end
it 'updates the snippet' do
mark_as_spam
expect(snippet.reload).not_to be_submittable_as_spam
end
end
%w[show raw].each do |action| %w[show raw].each do |action|
describe "GET ##{action}" do describe "GET ##{action}" do
context 'when the project snippet is private' do context 'when the project snippet is private' do
......
...@@ -138,6 +138,65 @@ describe SnippetsController do ...@@ -138,6 +138,65 @@ describe SnippetsController do
end end
end end
describe 'POST #create' do
def create_snippet(snippet_params = {})
sign_in(user)
post :create, {
personal_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params)
}
end
context 'when the snippet is spam' do
before do
allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
end
context 'when the snippet is private' do
it 'creates the snippet' do
expect { create_snippet(visibility_level: Snippet::PRIVATE) }.
to change { Snippet.count }.by(1)
end
end
context 'when the snippet is public' do
it 'rejects the shippet' do
expect { create_snippet(visibility_level: Snippet::PUBLIC) }.
not_to change { Snippet.count }
expect(response).to render_template(:new)
end
it 'creates a spam log' do
expect { create_snippet(visibility_level: Snippet::PUBLIC) }.
to change { SpamLog.count }.by(1)
end
end
end
end
describe 'POST #mark_as_spam' do
let(:snippet) { create(:personal_snippet, :public, author: user) }
before do
allow_any_instance_of(AkismetService).to receive_messages(submit_spam: true)
stub_application_setting(akismet_enabled: true)
end
def mark_as_spam
admin = create(:admin)
create(:user_agent_detail, subject: snippet)
sign_in(admin)
post :mark_as_spam, id: snippet.id
end
it 'updates the snippet' do
mark_as_spam
expect(snippet.reload).not_to be_submittable_as_spam
end
end
%w(raw download).each do |action| %w(raw download).each do |action|
describe "GET #{action}" do describe "GET #{action}" do
context 'when the personal snippet is private' do context 'when the personal snippet is private' do
......
require 'spec_helper'
feature 'Dashboard shortcuts', feature: true, js: true do
before do
login_as :user
visit dashboard_projects_path
end
scenario 'Navigate to tabs' do
find('body').native.send_key('g')
find('body').native.send_key('p')
ensure_active_main_tab('Projects')
find('body').native.send_key('g')
find('body').native.send_key('i')
ensure_active_main_tab('Issues')
find('body').native.send_key('g')
find('body').native.send_key('m')
ensure_active_main_tab('Merge Requests')
end
def ensure_active_main_tab(content)
expect(find('.nav-sidebar li.active')).to have_content(content)
end
end
...@@ -19,6 +19,10 @@ feature 'Environment', :feature do ...@@ -19,6 +19,10 @@ feature 'Environment', :feature do
visit_environment(environment) visit_environment(environment)
end end
scenario 'shows environment name' do
expect(page).to have_content(environment.name)
end
context 'without deployments' do context 'without deployments' do
scenario 'does show no deployments' do scenario 'does show no deployments' do
expect(page).to have_content('You don\'t have any deployments right now.') expect(page).to have_content('You don\'t have any deployments right now.')
......
...@@ -194,7 +194,7 @@ feature 'Environments page', :feature, :js do ...@@ -194,7 +194,7 @@ feature 'Environments page', :feature, :js do
end end
scenario 'does create a new pipeline' do scenario 'does create a new pipeline' do
expect(page).to have_content('Production') expect(page).to have_content('production')
end end
end end
......
...@@ -2,7 +2,7 @@ require 'rails_helper' ...@@ -2,7 +2,7 @@ require 'rails_helper'
feature 'GFM autocomplete', feature: true, js: true do feature 'GFM autocomplete', feature: true, js: true do
include WaitForAjax include WaitForAjax
let(:user) { create(:user, username: 'someone.special') } let(:user) { create(:user, name: '💃speciąl someone💃', username: 'someone.special') }
let(:project) { create(:project) } let(:project) { create(:project) }
let(:label) { create(:label, project: project, title: 'special+') } let(:label) { create(:label, project: project, title: 'special+') }
let(:issue) { create(:issue, project: project) } let(:issue) { create(:issue, project: project) }
...@@ -59,6 +59,19 @@ feature 'GFM autocomplete', feature: true, js: true do ...@@ -59,6 +59,19 @@ feature 'GFM autocomplete', feature: true, js: true do
expect(find('#at-view-64')).to have_selector('.cur:first-of-type') expect(find('#at-view-64')).to have_selector('.cur:first-of-type')
end end
it 'includes items for assignee dropdowns with non-ASCII characters in name' do
page.within '.timeline-content-form' do
find('#note_note').native.send_keys('')
find('#note_note').native.send_keys("@#{user.name[0...8]}")
end
expect(page).to have_selector('.atwho-container')
wait_for_ajax
expect(find('#at-view-64')).to have_content(user.name)
end
it 'selects the first item for non-assignee dropdowns if a query is entered' do it 'selects the first item for non-assignee dropdowns if a query is entered' do
page.within '.timeline-content-form' do page.within '.timeline-content-form' do
find('#note_note').native.send_keys('') find('#note_note').native.send_keys('')
......
require 'spec_helper'
feature 'toggler_behavior', js: true, feature: true do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:merge_request) { create(:merge_request, source_project: project, author: user) }
let(:note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project) }
let(:fragment_id) { "#note_#{note.id}" }
before do
login_as :admin
project = merge_request.source_project
visit "#{namespace_project_merge_request_path(project.namespace, project, merge_request)}#{fragment_id}"
page.current_window.resize_to(1000, 300)
end
describe 'scroll position' do
it 'should be scrolled down to fragment' do
page_height = page.current_window.size[1]
page_scroll_y = page.evaluate_script("window.scrollY")
fragment_position_top = page.evaluate_script("$('#{fragment_id}').offset().top")
expect(find('.js-toggle-content').visible?).to eq true
expect(find(fragment_id).visible?).to eq true
expect(fragment_position_top).to be >= page_scroll_y
expect(fragment_position_top).to be < (page_scroll_y + page_height)
end
end
end
...@@ -89,7 +89,7 @@ describe 'Comments', feature: true do ...@@ -89,7 +89,7 @@ describe 'Comments', feature: true do
end end
end end
it 'should reset the edit note form textarea with the original content of the note if cancelled' do it 'resets the edit note form textarea with the original content of the note if cancelled' do
within('.current-note-edit-form') do within('.current-note-edit-form') do
fill_in 'note[note]', with: 'Some new content' fill_in 'note[note]', with: 'Some new content'
find('.btn-cancel').click find('.btn-cancel').click
...@@ -198,7 +198,7 @@ describe 'Comments', feature: true do ...@@ -198,7 +198,7 @@ describe 'Comments', feature: true do
end end
describe 'the note form' do describe 'the note form' do
it "shouldn't add a second form for same row" do it "does not add a second form for same row" do
click_diff_line click_diff_line
is_expected. is_expected.
...@@ -206,7 +206,7 @@ describe 'Comments', feature: true do ...@@ -206,7 +206,7 @@ describe 'Comments', feature: true do
count: 1) count: 1)
end end
it 'should be removed when canceled' do it 'is removed when canceled' do
is_expected.to have_css('.js-temp-notes-holder') is_expected.to have_css('.js-temp-notes-holder')
page.within("form[data-line-code='#{line_code}']") do page.within("form[data-line-code='#{line_code}']") do
......
...@@ -134,7 +134,7 @@ describe DiffHelper do ...@@ -134,7 +134,7 @@ describe DiffHelper do
let(:new_pos) { 50 } let(:new_pos) { 50 }
let(:text) { 'some_text' } let(:text) { 'some_text' }
it "should generate foldable top match line for inline view with empty text by default" do it "generates foldable top match line for inline view with empty text by default" do
output = diff_match_line old_pos, new_pos output = diff_match_line old_pos, new_pos
expect(output).to be_html_safe expect(output).to be_html_safe
...@@ -143,7 +143,7 @@ describe DiffHelper do ...@@ -143,7 +143,7 @@ describe DiffHelper do
expect(output).to have_css 'td:nth-child(3):not(.parallel).line_content.match', text: '' expect(output).to have_css 'td:nth-child(3):not(.parallel).line_content.match', text: ''
end end
it "should allow to define text and bottom option" do it "allows to define text and bottom option" do
output = diff_match_line old_pos, new_pos, text: text, bottom: true output = diff_match_line old_pos, new_pos, text: text, bottom: true
expect(output).to be_html_safe expect(output).to be_html_safe
...@@ -152,7 +152,7 @@ describe DiffHelper do ...@@ -152,7 +152,7 @@ describe DiffHelper do
expect(output).to have_css 'td:nth-child(3):not(.parallel).line_content.match', text: text expect(output).to have_css 'td:nth-child(3):not(.parallel).line_content.match', text: text
end end
it "should generate match line for parallel view" do it "generates match line for parallel view" do
output = diff_match_line old_pos, new_pos, text: text, view: :parallel output = diff_match_line old_pos, new_pos, text: text, view: :parallel
expect(output).to be_html_safe expect(output).to be_html_safe
...@@ -162,7 +162,7 @@ describe DiffHelper do ...@@ -162,7 +162,7 @@ describe DiffHelper do
expect(output).to have_css 'td:nth-child(4).line_content.match.parallel', text: text expect(output).to have_css 'td:nth-child(4).line_content.match.parallel', text: text
end end
it "should allow to generate only left match line for parallel view" do it "allows to generate only left match line for parallel view" do
output = diff_match_line old_pos, nil, text: text, view: :parallel output = diff_match_line old_pos, nil, text: text, view: :parallel
expect(output).to be_html_safe expect(output).to be_html_safe
...@@ -171,7 +171,7 @@ describe DiffHelper do ...@@ -171,7 +171,7 @@ describe DiffHelper do
expect(output).not_to have_css 'td:nth-child(3)' expect(output).not_to have_css 'td:nth-child(3)'
end end
it "should allow to generate only right match line for parallel view" do it "allows to generate only right match line for parallel view" do
output = diff_match_line nil, new_pos, text: text, view: :parallel output = diff_match_line nil, new_pos, text: text, view: :parallel
expect(output).to be_html_safe expect(output).to be_html_safe
......
...@@ -111,7 +111,7 @@ require('./fixtures/emoji_menu'); ...@@ -111,7 +111,7 @@ require('./fixtures/emoji_menu');
}); });
}); });
describe('::getAwardUrl', function() { describe('::getAwardUrl', function() {
return it('should return the url for request', function() { return it('returns the url for request', function() {
return expect(awardsHandler.getAwardUrl()).toBe('http://test.host/frontend-fixtures/issues-project/issues/1/toggle_award_emoji'); return expect(awardsHandler.getAwardUrl()).toBe('http://test.host/frontend-fixtures/issues-project/issues/1/toggle_award_emoji');
}); });
}); });
......
require('~/filtered_search/dropdown_utils');
require('~/filtered_search/filtered_search_tokenizer');
require('~/filtered_search/filtered_search_dropdown');
require('~/filtered_search/dropdown_user');
(() => {
describe('Dropdown User', () => {
describe('getSearchInput', () => {
let dropdownUser;
beforeEach(() => {
spyOn(gl.FilteredSearchDropdown.prototype, 'constructor').and.callFake(() => {});
spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {});
spyOn(gl.DropdownUtils, 'getSearchInput').and.callFake(() => {});
dropdownUser = new gl.DropdownUser();
});
it('should not return the double quote found in value', () => {
spyOn(gl.FilteredSearchTokenizer, 'processTokens').and.returnValue({
lastToken: {
value: '"johnny appleseed',
},
});
expect(dropdownUser.getSearchInput()).toBe('johnny appleseed');
});
it('should not return the single quote found in value', () => {
spyOn(gl.FilteredSearchTokenizer, 'processTokens').and.returnValue({
lastToken: {
value: '\'larry boy',
},
});
expect(dropdownUser.getSearchInput()).toBe('larry boy');
});
});
});
})();
...@@ -22,6 +22,7 @@ require('~/filtered_search/filtered_search_manager'); ...@@ -22,6 +22,7 @@ require('~/filtered_search/filtered_search_manager');
`); `);
spyOn(gl.FilteredSearchManager.prototype, 'bindEvents').and.callFake(() => {}); spyOn(gl.FilteredSearchManager.prototype, 'bindEvents').and.callFake(() => {});
spyOn(gl.FilteredSearchManager.prototype, 'cleanup').and.callFake(() => {});
spyOn(gl.FilteredSearchManager.prototype, 'loadSearchParamsFromURL').and.callFake(() => {}); spyOn(gl.FilteredSearchManager.prototype, 'loadSearchParamsFromURL').and.callFake(() => {});
spyOn(gl.FilteredSearchDropdownManager.prototype, 'setDropdown').and.callFake(() => {}); spyOn(gl.FilteredSearchDropdownManager.prototype, 'setDropdown').and.callFake(() => {});
spyOn(gl.utils, 'getParameterByName').and.returnValue(null); spyOn(gl.utils, 'getParameterByName').and.returnValue(null);
......
/* eslint-disable space-before-function-paren, quotes, comma-dangle, dot-notation, quote-props, no-var, max-len */ /* eslint-disable space-before-function-paren, quotes, comma-dangle, dot-notation, quote-props, no-var, max-len */
require('~/merge_request_widget'); require('~/merge_request_widget');
require('~/smart_interval');
require('~/lib/utils/datetime_utility'); require('~/lib/utils/datetime_utility');
(function() { (function() {
...@@ -21,7 +22,11 @@ require('~/lib/utils/datetime_utility'); ...@@ -21,7 +22,11 @@ require('~/lib/utils/datetime_utility');
normal: "Build {{status}}" normal: "Build {{status}}"
}, },
gitlab_icon: "gitlab_logo.png", gitlab_icon: "gitlab_logo.png",
builds_path: "http://sampledomain.local/sampleBuildsPath" ci_pipeline: 80,
ci_sha: "12a34bc5",
builds_path: "http://sampledomain.local/sampleBuildsPath",
commits_path: "http://sampledomain.local/commits",
pipeline_path: "http://sampledomain.local/pipelines"
}; };
this["class"] = new window.gl.MergeRequestWidget(this.opts); this["class"] = new window.gl.MergeRequestWidget(this.opts);
}); });
...@@ -118,10 +123,11 @@ require('~/lib/utils/datetime_utility'); ...@@ -118,10 +123,11 @@ require('~/lib/utils/datetime_utility');
}); });
}); });
return describe('getCIStatus', function() { describe('getCIStatus', function() {
beforeEach(function() { beforeEach(function() {
this.ciStatusData = { this.ciStatusData = {
"title": "Sample MR title", "title": "Sample MR title",
"pipeline": 80,
"sha": "12a34bc5", "sha": "12a34bc5",
"status": "success", "status": "success",
"coverage": 98 "coverage": 98
...@@ -165,6 +171,22 @@ require('~/lib/utils/datetime_utility'); ...@@ -165,6 +171,22 @@ require('~/lib/utils/datetime_utility');
this["class"].getCIStatus(true); this["class"].getCIStatus(true);
return expect(spy).not.toHaveBeenCalled(); return expect(spy).not.toHaveBeenCalled();
}); });
it('should update the pipeline URL when the pipeline changes', function() {
var spy;
spy = spyOn(this["class"], 'updatePipelineUrls').and.stub();
this["class"].getCIStatus(false);
this.ciStatusData.pipeline += 1;
this["class"].getCIStatus(false);
return expect(spy).toHaveBeenCalled();
});
it('should update the commit URL when the sha changes', function() {
var spy;
spy = spyOn(this["class"], 'updateCommitUrls').and.stub();
this["class"].getCIStatus(false);
this.ciStatusData.sha = "9b50b99a";
this["class"].getCIStatus(false);
return expect(spy).toHaveBeenCalled();
});
}); });
}); });
}).call(this); }).call(this);
...@@ -11,9 +11,9 @@ require('~/shortcuts_issuable'); ...@@ -11,9 +11,9 @@ require('~/shortcuts_issuable');
beforeEach(function() { beforeEach(function() {
loadFixtures(fixtureName); loadFixtures(fixtureName);
document.querySelector('.js-new-note-form').classList.add('js-main-target-form'); document.querySelector('.js-new-note-form').classList.add('js-main-target-form');
return this.shortcut = new ShortcutsIssuable(); this.shortcut = new ShortcutsIssuable();
}); });
return describe('#replyWithSelectedText', function() { describe('#replyWithSelectedText', function() {
var stubSelection; var stubSelection;
// Stub window.gl.utils.getSelectedFragment to return a node with the provided HTML. // Stub window.gl.utils.getSelectedFragment to return a node with the provided HTML.
stubSelection = function(html) { stubSelection = function(html) {
...@@ -24,51 +24,57 @@ require('~/shortcuts_issuable'); ...@@ -24,51 +24,57 @@ require('~/shortcuts_issuable');
}; };
}; };
beforeEach(function() { beforeEach(function() {
return this.selector = 'form.js-main-target-form textarea#note_note'; this.selector = 'form.js-main-target-form textarea#note_note';
}); });
describe('with empty selection', function() { describe('with empty selection', function() {
return it('does nothing', function() { it('does not return an error', function() {
stubSelection('');
this.shortcut.replyWithSelectedText(); this.shortcut.replyWithSelectedText();
return expect($(this.selector).val()).toBe(''); expect($(this.selector).val()).toBe('');
});
it('triggers `input`', function() {
var focused = false;
$(this.selector).on('focus', function() {
focused = true;
});
this.shortcut.replyWithSelectedText();
expect(focused).toBe(true);
}); });
}); });
describe('with any selection', function() { describe('with any selection', function() {
beforeEach(function() { beforeEach(function() {
return stubSelection('<p>Selected text.</p>'); stubSelection('<p>Selected text.</p>');
}); });
it('leaves existing input intact', function() { it('leaves existing input intact', function() {
$(this.selector).val('This text was already here.'); $(this.selector).val('This text was already here.');
expect($(this.selector).val()).toBe('This text was already here.'); expect($(this.selector).val()).toBe('This text was already here.');
this.shortcut.replyWithSelectedText(); this.shortcut.replyWithSelectedText();
return expect($(this.selector).val()).toBe("This text was already here.\n\n> Selected text.\n\n"); expect($(this.selector).val()).toBe("This text was already here.\n\n> Selected text.\n\n");
}); });
it('triggers `input`', function() { it('triggers `input`', function() {
var triggered; var triggered = false;
triggered = false;
$(this.selector).on('input', function() { $(this.selector).on('input', function() {
return triggered = true; triggered = true;
}); });
this.shortcut.replyWithSelectedText(); this.shortcut.replyWithSelectedText();
return expect(triggered).toBe(true); expect(triggered).toBe(true);
}); });
return it('triggers `focus`', function() { it('triggers `focus`', function() {
this.shortcut.replyWithSelectedText(); this.shortcut.replyWithSelectedText();
expect(document.activeElement).toBe(document.querySelector(this.selector)); expect(document.activeElement).toBe(document.querySelector(this.selector));
}); });
}); });
describe('with a one-line selection', function() { describe('with a one-line selection', function() {
return it('quotes the selection', function() { it('quotes the selection', function() {
stubSelection('<p>This text has been selected.</p>'); stubSelection('<p>This text has been selected.</p>');
this.shortcut.replyWithSelectedText(); this.shortcut.replyWithSelectedText();
return expect($(this.selector).val()).toBe("> This text has been selected.\n\n"); expect($(this.selector).val()).toBe("> This text has been selected.\n\n");
}); });
}); });
return describe('with a multi-line selection', function() { describe('with a multi-line selection', function() {
return it('quotes the selected lines as a group', function() { it('quotes the selected lines as a group', function() {
stubSelection("<p>Selected line one.</p>\n\n<p>Selected line two.</p>\n\n<p>Selected line three.</p>"); stubSelection("<p>Selected line one.</p>\n\n<p>Selected line two.</p>\n\n<p>Selected line three.</p>");
this.shortcut.replyWithSelectedText(); this.shortcut.replyWithSelectedText();
return expect($(this.selector).val()).toBe("> Selected line one.\n>\n> Selected line two.\n>\n> Selected line three.\n\n"); expect($(this.selector).val()).toBe("> Selected line one.\n>\n> Selected line two.\n>\n> Selected line three.\n\n");
}); });
}); });
}); });
......
...@@ -4,6 +4,33 @@ module Ci ...@@ -4,6 +4,33 @@ module Ci
describe GitlabCiYamlProcessor, lib: true do describe GitlabCiYamlProcessor, lib: true do
let(:path) { 'path' } let(:path) { 'path' }
describe '#build_attributes' do
context 'Coverage entry' do
subject { described_class.new(config, path).build_attributes(:rspec) }
let(:config_base) { { rspec: { script: "rspec" } } }
let(:config) { YAML.dump(config_base) }
context 'when config has coverage set at the global scope' do
before do
config_base.update(coverage: '/\(\d+\.\d+\) covered/')
end
context "and 'rspec' job doesn't have coverage set" do
it { is_expected.to include(coverage_regex: '\(\d+\.\d+\) covered') }
end
context "but 'rspec' job also has coverage set" do
before do
config_base[:rspec][:coverage] = '/Code coverage: \d+\.\d+/'
end
it { is_expected.to include(coverage_regex: 'Code coverage: \d+\.\d+') }
end
end
end
end
describe "#builds_for_ref" do describe "#builds_for_ref" do
let(:type) { 'test' } let(:type) { 'test' }
...@@ -21,6 +48,7 @@ module Ci ...@@ -21,6 +48,7 @@ module Ci
stage_idx: 1, stage_idx: 1,
name: "rspec", name: "rspec",
commands: "pwd\nrspec", commands: "pwd\nrspec",
coverage_regex: nil,
tag_list: [], tag_list: [],
options: {}, options: {},
allow_failure: false, allow_failure: false,
...@@ -435,6 +463,7 @@ module Ci ...@@ -435,6 +463,7 @@ module Ci
stage_idx: 1, stage_idx: 1,
name: "rspec", name: "rspec",
commands: "pwd\nrspec", commands: "pwd\nrspec",
coverage_regex: nil,
tag_list: [], tag_list: [],
options: { options: {
image: "ruby:2.1", image: "ruby:2.1",
...@@ -463,6 +492,7 @@ module Ci ...@@ -463,6 +492,7 @@ module Ci
stage_idx: 1, stage_idx: 1,
name: "rspec", name: "rspec",
commands: "pwd\nrspec", commands: "pwd\nrspec",
coverage_regex: nil,
tag_list: [], tag_list: [],
options: { options: {
image: "ruby:2.5", image: "ruby:2.5",
...@@ -702,6 +732,7 @@ module Ci ...@@ -702,6 +732,7 @@ module Ci
stage_idx: 1, stage_idx: 1,
name: "rspec", name: "rspec",
commands: "pwd\nrspec", commands: "pwd\nrspec",
coverage_regex: nil,
tag_list: [], tag_list: [],
options: { options: {
image: "ruby:2.1", image: "ruby:2.1",
...@@ -913,6 +944,7 @@ module Ci ...@@ -913,6 +944,7 @@ module Ci
stage_idx: 1, stage_idx: 1,
name: "normal_job", name: "normal_job",
commands: "test", commands: "test",
coverage_regex: nil,
tag_list: [], tag_list: [],
options: {}, options: {},
when: "on_success", when: "on_success",
...@@ -958,6 +990,7 @@ module Ci ...@@ -958,6 +990,7 @@ module Ci
stage_idx: 0, stage_idx: 0,
name: "job1", name: "job1",
commands: "execute-script-for-job", commands: "execute-script-for-job",
coverage_regex: nil,
tag_list: [], tag_list: [],
options: {}, options: {},
when: "on_success", when: "on_success",
...@@ -970,6 +1003,7 @@ module Ci ...@@ -970,6 +1003,7 @@ module Ci
stage_idx: 0, stage_idx: 0,
name: "job2", name: "job2",
commands: "execute-script-for-job", commands: "execute-script-for-job",
coverage_regex: nil,
tag_list: [], tag_list: [],
options: {}, options: {},
when: "on_success", when: "on_success",
......
...@@ -58,6 +58,7 @@ describe Gitlab::Auth, lib: true do ...@@ -58,6 +58,7 @@ describe Gitlab::Auth, lib: true do
expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities)) expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
end end
context 'while using LFS authenticate' do
it 'recognizes user lfs tokens' do it 'recognizes user lfs tokens' do
user = create(:user) user = create(:user)
token = Gitlab::LfsToken.new(user).token token = Gitlab::LfsToken.new(user).token
...@@ -74,42 +75,85 @@ describe Gitlab::Auth, lib: true do ...@@ -74,42 +75,85 @@ describe Gitlab::Auth, lib: true do
expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_authentication_abilities)) expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_authentication_abilities))
end end
context "while using OAuth tokens as passwords" do it 'does not try password auth before oauth' do
it 'succeeds for OAuth tokens with the `api` scope' do
user = create(:user) user = create(:user)
application = Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user) token = Gitlab::LfsToken.new(user).token
token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: "api")
expect(gl_auth).not_to receive(:find_with_user_password)
gl_auth.find_for_git_client(user.username, token, project: nil, ip: 'ip')
end
end
context 'while using OAuth tokens as passwords' do
let(:user) { create(:user) }
let(:token_w_api_scope) { Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: 'api') }
let(:application) { Doorkeeper::Application.create!(name: 'MyApp', redirect_uri: 'https://app.com', owner: user) }
it 'succeeds for OAuth tokens with the `api` scope' do
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'oauth2') expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'oauth2')
expect(gl_auth.find_for_git_client("oauth2", token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :oauth, read_authentication_abilities)) expect(gl_auth.find_for_git_client("oauth2", token_w_api_scope.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :oauth, read_authentication_abilities))
end end
it 'fails for OAuth tokens with other scopes' do it 'fails for OAuth tokens with other scopes' do
user = create(:user) token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: 'read_user')
application = Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user)
token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: "read_user")
expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'oauth2') expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'oauth2')
expect(gl_auth.find_for_git_client("oauth2", token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil)) expect(gl_auth.find_for_git_client("oauth2", token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil))
end end
it 'does not try password auth before oauth' do
expect(gl_auth).not_to receive(:find_with_user_password)
gl_auth.find_for_git_client("oauth2", token_w_api_scope.token, project: nil, ip: 'ip')
end
end end
context "while using personal access tokens as passwords" do context 'while using personal access tokens as passwords' do
it 'succeeds for personal access tokens with the `api` scope' do let(:user) { create(:user) }
user = create(:user) let(:token_w_api_scope) { create(:personal_access_token, user: user, scopes: ['api']) }
personal_access_token = create(:personal_access_token, user: user, scopes: ['api'])
it 'succeeds for personal access tokens with the `api` scope' do
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: user.email) expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: user.email)
expect(gl_auth.find_for_git_client(user.email, personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :personal_token, full_authentication_abilities)) expect(gl_auth.find_for_git_client(user.email, token_w_api_scope.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :personal_token, full_authentication_abilities))
end end
it 'fails for personal access tokens with other scopes' do it 'fails for personal access tokens with other scopes' do
user = create(:user)
personal_access_token = create(:personal_access_token, user: user, scopes: ['read_user']) personal_access_token = create(:personal_access_token, user: user, scopes: ['read_user'])
expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: user.email) expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: user.email)
expect(gl_auth.find_for_git_client(user.email, personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil)) expect(gl_auth.find_for_git_client(user.email, personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil))
end end
it 'does not try password auth before personal access tokens' do
expect(gl_auth).not_to receive(:find_with_user_password)
gl_auth.find_for_git_client(user.email, token_w_api_scope.token, project: nil, ip: 'ip')
end
end
context 'while using regular user and password' do
it 'falls through lfs authentication' do
user = create(
:user,
username: 'normal_user',
password: 'my-secret',
)
expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
.to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
end
it 'falls through oauth authentication when the username is oauth2' do
user = create(
:user,
username: 'oauth2',
password: 'my-secret',
)
expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
.to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
end
end end
it 'returns double nil for invalid credentials' do it 'returns double nil for invalid credentials' do
......
...@@ -24,7 +24,7 @@ describe Gitlab::ChatCommands::Command, service: true do ...@@ -24,7 +24,7 @@ describe Gitlab::ChatCommands::Command, service: true do
it 'displays the help message' do it 'displays the help message' do
expect(subject[:response_type]).to be(:ephemeral) expect(subject[:response_type]).to be(:ephemeral)
expect(subject[:text]).to start_with('Available commands') expect(subject[:text]).to start_with('Unknown command')
expect(subject[:text]).to match('/gitlab issue show') expect(subject[:text]).to match('/gitlab issue show')
end end
end end
...@@ -34,47 +34,7 @@ describe Gitlab::ChatCommands::Command, service: true do ...@@ -34,47 +34,7 @@ describe Gitlab::ChatCommands::Command, service: true do
it 'rejects the actions' do it 'rejects the actions' do
expect(subject[:response_type]).to be(:ephemeral) expect(subject[:response_type]).to be(:ephemeral)
expect(subject[:text]).to start_with('Whoops! That action is not allowed') expect(subject[:text]).to start_with('Whoops! This action is not allowed')
end
end
context 'issue is successfully created' do
let(:params) { { text: "issue create my new issue" } }
before do
project.team << [user, :master]
end
it 'presents the issue' do
expect(subject[:text]).to match("my new issue")
end
it 'shows a link to the new issue' do
expect(subject[:text]).to match(/\/issues\/\d+/)
end
end
context 'searching for an issue' do
let(:params) { { text: 'issue search find me' } }
let!(:issue) { create(:issue, project: project, title: 'find me') }
before do
project.team << [user, :master]
end
context 'a single issue is found' do
it 'presents the issue' do
expect(subject[:text]).to match(issue.title)
end
end
context 'multiple issues found' do
let!(:issue2) { create(:issue, project: project, title: "someone find me") }
it 'shows a link to the new issue' do
expect(subject[:text]).to match(issue.title)
expect(subject[:text]).to match(issue2.title)
end
end end
end end
...@@ -90,7 +50,7 @@ describe Gitlab::ChatCommands::Command, service: true do ...@@ -90,7 +50,7 @@ describe Gitlab::ChatCommands::Command, service: true do
context 'and user can not create deployment' do context 'and user can not create deployment' do
it 'returns action' do it 'returns action' do
expect(subject[:response_type]).to be(:ephemeral) expect(subject[:response_type]).to be(:ephemeral)
expect(subject[:text]).to start_with('Whoops! That action is not allowed') expect(subject[:text]).to start_with('Whoops! This action is not allowed')
end end
end end
...@@ -100,7 +60,7 @@ describe Gitlab::ChatCommands::Command, service: true do ...@@ -100,7 +60,7 @@ describe Gitlab::ChatCommands::Command, service: true do
end end
it 'returns action' do it 'returns action' do
expect(subject[:text]).to include('Deployment from staging to production started.') expect(subject[:text]).to include('Deployment started from staging to production')
expect(subject[:response_type]).to be(:in_channel) expect(subject[:response_type]).to be(:in_channel)
end end
...@@ -130,7 +90,7 @@ describe Gitlab::ChatCommands::Command, service: true do ...@@ -130,7 +90,7 @@ describe Gitlab::ChatCommands::Command, service: true do
context 'IssueCreate is triggered' do context 'IssueCreate is triggered' do
let(:params) { { text: 'issue create my title' } } let(:params) { { text: 'issue create my title' } }
it { is_expected.to eq(Gitlab::ChatCommands::IssueCreate) } it { is_expected.to eq(Gitlab::ChatCommands::IssueNew) }
end end
context 'IssueSearch is triggered' do context 'IssueSearch is triggered' do
......
...@@ -15,8 +15,9 @@ describe Gitlab::ChatCommands::Deploy, service: true do ...@@ -15,8 +15,9 @@ describe Gitlab::ChatCommands::Deploy, service: true do
end end
context 'if no environment is defined' do context 'if no environment is defined' do
it 'returns nil' do it 'does not execute an action' do
expect(subject).to be_nil expect(subject[:response_type]).to be(:ephemeral)
expect(subject[:text]).to eq("No action found to be executed")
end end
end end
...@@ -26,8 +27,9 @@ describe Gitlab::ChatCommands::Deploy, service: true do ...@@ -26,8 +27,9 @@ describe Gitlab::ChatCommands::Deploy, service: true do
let!(:deployment) { create(:deployment, environment: staging, deployable: build) } let!(:deployment) { create(:deployment, environment: staging, deployable: build) }
context 'without actions' do context 'without actions' do
it 'returns nil' do it 'does not execute an action' do
expect(subject).to be_nil expect(subject[:response_type]).to be(:ephemeral)
expect(subject[:text]).to eq("No action found to be executed")
end end
end end
...@@ -37,8 +39,8 @@ describe Gitlab::ChatCommands::Deploy, service: true do ...@@ -37,8 +39,8 @@ describe Gitlab::ChatCommands::Deploy, service: true do
end end
it 'returns success result' do it 'returns success result' do
expect(subject.type).to eq(:success) expect(subject[:response_type]).to be(:in_channel)
expect(subject.message).to include('Deployment from staging to production started') expect(subject[:text]).to start_with('Deployment started from staging to production')
end end
context 'when duplicate action exists' do context 'when duplicate action exists' do
...@@ -47,8 +49,8 @@ describe Gitlab::ChatCommands::Deploy, service: true do ...@@ -47,8 +49,8 @@ describe Gitlab::ChatCommands::Deploy, service: true do
end end
it 'returns error' do it 'returns error' do
expect(subject.type).to eq(:error) expect(subject[:response_type]).to be(:ephemeral)
expect(subject.message).to include('Too many actions defined') expect(subject[:text]).to eq('Too many actions defined')
end end
end end
...@@ -59,9 +61,9 @@ describe Gitlab::ChatCommands::Deploy, service: true do ...@@ -59,9 +61,9 @@ describe Gitlab::ChatCommands::Deploy, service: true do
name: 'teardown', environment: 'production') name: 'teardown', environment: 'production')
end end
it 'returns success result' do it 'returns the success message' do
expect(subject.type).to eq(:success) expect(subject[:response_type]).to be(:in_channel)
expect(subject.message).to include('Deployment from staging to production started') expect(subject[:text]).to start_with('Deployment started from staging to production')
end end
end end
end end
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::ChatCommands::IssueCreate, service: true do describe Gitlab::ChatCommands::IssueNew, service: true do
describe '#execute' do describe '#execute' do
let(:project) { create(:empty_project) } let(:project) { create(:empty_project) }
let(:user) { create(:user) } let(:user) { create(:user) }
...@@ -18,7 +18,7 @@ describe Gitlab::ChatCommands::IssueCreate, service: true do ...@@ -18,7 +18,7 @@ describe Gitlab::ChatCommands::IssueCreate, service: true do
it 'creates the issue' do it 'creates the issue' do
expect { subject }.to change { project.issues.count }.by(1) expect { subject }.to change { project.issues.count }.by(1)
expect(subject.title).to eq('bird is the word') expect(subject[:response_type]).to be(:in_channel)
end end
end end
...@@ -41,6 +41,16 @@ describe Gitlab::ChatCommands::IssueCreate, service: true do ...@@ -41,6 +41,16 @@ describe Gitlab::ChatCommands::IssueCreate, service: true do
expect { subject }.to change { project.issues.count }.by(1) expect { subject }.to change { project.issues.count }.by(1)
end end
end end
context 'issue cannot be created' do
let!(:issue) { create(:issue, project: project, title: 'bird is the word') }
let(:regex_match) { described_class.match("issue create #{'a' * 512}}") }
it 'displays the errors' do
expect(subject[:response_type]).to be(:ephemeral)
expect(subject[:text]).to match("- Title is too long")
end
end
end end
describe '.match' do describe '.match' do
......
...@@ -2,9 +2,9 @@ require 'spec_helper' ...@@ -2,9 +2,9 @@ require 'spec_helper'
describe Gitlab::ChatCommands::IssueSearch, service: true do describe Gitlab::ChatCommands::IssueSearch, service: true do
describe '#execute' do describe '#execute' do
let!(:issue) { create(:issue, title: 'find me') } let!(:issue) { create(:issue, project: project, title: 'find me') }
let!(:confidential) { create(:issue, :confidential, project: project, title: 'mepmep find') } let!(:confidential) { create(:issue, :confidential, project: project, title: 'mepmep find') }
let(:project) { issue.project } let(:project) { create(:empty_project) }
let(:user) { issue.author } let(:user) { issue.author }
let(:regex_match) { described_class.match("issue search find") } let(:regex_match) { described_class.match("issue search find") }
...@@ -14,7 +14,8 @@ describe Gitlab::ChatCommands::IssueSearch, service: true do ...@@ -14,7 +14,8 @@ describe Gitlab::ChatCommands::IssueSearch, service: true do
context 'when the user has no access' do context 'when the user has no access' do
it 'only returns the open issues' do it 'only returns the open issues' do
expect(subject).not_to include(confidential) expect(subject[:response_type]).to be(:ephemeral)
expect(subject[:text]).to match("not found")
end end
end end
...@@ -24,13 +25,14 @@ describe Gitlab::ChatCommands::IssueSearch, service: true do ...@@ -24,13 +25,14 @@ describe Gitlab::ChatCommands::IssueSearch, service: true do
end end
it 'returns all results' do it 'returns all results' do
expect(subject).to include(confidential, issue) expect(subject).to have_key(:attachments)
expect(subject[:text]).to eq("Here are the 2 issues I found:")
end end
end end
context 'without hits on the query' do context 'without hits on the query' do
it 'returns an empty collection' do it 'returns an empty collection' do
expect(subject).to be_empty expect(subject[:text]).to match("not found")
end end
end end
end end
......
...@@ -2,8 +2,8 @@ require 'spec_helper' ...@@ -2,8 +2,8 @@ require 'spec_helper'
describe Gitlab::ChatCommands::IssueShow, service: true do describe Gitlab::ChatCommands::IssueShow, service: true do
describe '#execute' do describe '#execute' do
let(:issue) { create(:issue) } let(:issue) { create(:issue, project: project) }
let(:project) { issue.project } let(:project) { create(:empty_project) }
let(:user) { issue.author } let(:user) { issue.author }
let(:regex_match) { described_class.match("issue show #{issue.iid}") } let(:regex_match) { described_class.match("issue show #{issue.iid}") }
...@@ -16,15 +16,19 @@ describe Gitlab::ChatCommands::IssueShow, service: true do ...@@ -16,15 +16,19 @@ describe Gitlab::ChatCommands::IssueShow, service: true do
end end
context 'the issue exists' do context 'the issue exists' do
let(:title) { subject[:attachments].first[:title] }
it 'returns the issue' do it 'returns the issue' do
expect(subject.iid).to be issue.iid expect(subject[:response_type]).to be(:in_channel)
expect(title).to start_with(issue.title)
end end
context 'when its reference is given' do context 'when its reference is given' do
let(:regex_match) { described_class.match("issue show #{issue.to_reference}") } let(:regex_match) { described_class.match("issue show #{issue.to_reference}") }
it 'shows the issue' do it 'shows the issue' do
expect(subject.iid).to be issue.iid expect(subject[:response_type]).to be(:in_channel)
expect(title).to start_with(issue.title)
end end
end end
end end
...@@ -32,17 +36,24 @@ describe Gitlab::ChatCommands::IssueShow, service: true do ...@@ -32,17 +36,24 @@ describe Gitlab::ChatCommands::IssueShow, service: true do
context 'the issue does not exist' do context 'the issue does not exist' do
let(:regex_match) { described_class.match("issue show 2343242") } let(:regex_match) { described_class.match("issue show 2343242") }
it "returns nil" do it "returns not found" do
expect(subject).to be_nil expect(subject[:response_type]).to be(:ephemeral)
expect(subject[:text]).to match("not found")
end end
end end
end end
describe 'self.match' do describe '.match' do
it 'matches the iid' do it 'matches the iid' do
match = described_class.match("issue show 123") match = described_class.match("issue show 123")
expect(match[:iid]).to eq("123") expect(match[:iid]).to eq("123")
end end
it 'accepts a reference' do
match = described_class.match("issue show #{Issue.reference_prefix}123")
expect(match[:iid]).to eq("123")
end
end end
end end
require 'spec_helper'
describe Gitlab::ChatCommands::Presenters::Access do
describe '#access_denied' do
subject { described_class.new.access_denied }
it { is_expected.to be_a(Hash) }
it 'displays an error message' do
expect(subject[:text]).to match("is not allowed")
expect(subject[:response_type]).to be(:ephemeral)
end
end
describe '#not_found' do
subject { described_class.new.not_found }
it { is_expected.to be_a(Hash) }
it 'tells the user the resource was not found' do
expect(subject[:text]).to match("not found!")
expect(subject[:response_type]).to be(:ephemeral)
end
end
describe '#authorize' do
context 'with an authorization URL' do
subject { described_class.new('http://authorize.me').authorize }
it { is_expected.to be_a(Hash) }
it 'tells the user to authorize' do
expect(subject[:text]).to match("connect your GitLab account")
expect(subject[:response_type]).to be(:ephemeral)
end
end
context 'without authorization url' do
subject { described_class.new.authorize }
it { is_expected.to be_a(Hash) }
it 'tells the user to authorize' do
expect(subject[:text]).to match("Couldn't identify you")
expect(subject[:response_type]).to be(:ephemeral)
end
end
end
end
require 'spec_helper'
describe Gitlab::ChatCommands::Presenters::Deploy do
let(:build) { create(:ci_build) }
describe '#present' do
subject { described_class.new(build).present('staging', 'prod') }
it { is_expected.to have_key(:text) }
it { is_expected.to have_key(:response_type) }
it { is_expected.to have_key(:status) }
it { is_expected.not_to have_key(:attachments) }
it 'messages the channel of the deploy' do
expect(subject[:response_type]).to be(:in_channel)
expect(subject[:text]).to start_with("Deployment started from staging to prod")
end
end
describe '#no_actions' do
subject { described_class.new(nil).no_actions }
it { is_expected.to have_key(:text) }
it { is_expected.to have_key(:response_type) }
it { is_expected.to have_key(:status) }
it { is_expected.not_to have_key(:attachments) }
it 'tells the user there is no action' do
expect(subject[:response_type]).to be(:ephemeral)
expect(subject[:text]).to eq("No action found to be executed")
end
end
describe '#too_many_actions' do
subject { described_class.new([]).too_many_actions }
it { is_expected.to have_key(:text) }
it { is_expected.to have_key(:response_type) }
it { is_expected.to have_key(:status) }
it { is_expected.not_to have_key(:attachments) }
it 'tells the user there is no action' do
expect(subject[:response_type]).to be(:ephemeral)
expect(subject[:text]).to eq("Too many actions defined")
end
end
end
require 'spec_helper'
describe Gitlab::ChatCommands::Presenters::IssueNew do
let(:project) { create(:empty_project) }
let(:issue) { create(:issue, project: project) }
let(:attachment) { subject[:attachments].first }
subject { described_class.new(issue).present }
it { is_expected.to be_a(Hash) }
it 'shows the issue' do
expect(subject[:response_type]).to be(:in_channel)
expect(subject).to have_key(:attachments)
expect(attachment[:title]).to start_with(issue.title)
end
end
require 'spec_helper'
describe Gitlab::ChatCommands::Presenters::IssueSearch do
let(:project) { create(:empty_project) }
let(:message) { subject[:text] }
before { create_list(:issue, 2, project: project) }
subject { described_class.new(project.issues).present }
it 'formats the message correct' do
is_expected.to have_key(:text)
is_expected.to have_key(:status)
is_expected.to have_key(:response_type)
is_expected.to have_key(:attachments)
end
it 'shows a list of results' do
expect(subject[:response_type]).to be(:ephemeral)
expect(message).to start_with("Here are the 2 issues I found")
end
end
require 'spec_helper'
describe Gitlab::ChatCommands::Presenters::IssueShow do
let(:project) { create(:empty_project) }
let(:issue) { create(:issue, project: project) }
let(:attachment) { subject[:attachments].first }
subject { described_class.new(issue).present }
it { is_expected.to be_a(Hash) }
it 'shows the issue' do
expect(subject[:response_type]).to be(:in_channel)
expect(subject).to have_key(:attachments)
expect(attachment[:title]).to start_with(issue.title)
end
context 'with upvotes' do
before do
create(:award_emoji, :upvote, awardable: issue)
end
it 'shows the upvote count' do
expect(subject[:response_type]).to be(:in_channel)
expect(attachment[:text]).to start_with("**Open** · :+1: 1")
end
end
context 'confidential issue' do
let(:issue) { create(:issue, project: project) }
it 'shows an ephemeral response' do
expect(subject[:response_type]).to be(:in_channel)
expect(attachment[:text]).to start_with("**Open**")
end
end
end
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Coverage do
let(:entry) { described_class.new(config) }
describe 'validations' do
context "when entry config value doesn't have the surrounding '/'" do
let(:config) { 'Code coverage: \d+\.\d+' }
describe '#errors' do
subject { entry.errors }
it { is_expected.to include(/coverage config must be a regular expression/) }
end
describe '#valid?' do
subject { entry }
it { is_expected.not_to be_valid }
end
end
context "when entry config value has the surrounding '/'" do
let(:config) { '/Code coverage: \d+\.\d+/' }
describe '#value' do
subject { entry.value }
it { is_expected.to eq(config[1...-1]) }
end
describe '#errors' do
subject { entry.errors }
it { is_expected.to be_empty }
end
describe '#valid?' do
subject { entry }
it { is_expected.to be_valid }
end
end
context 'when entry value is not valid' do
let(:config) { '(malformed regexp' }
describe '#errors' do
subject { entry.errors }
it { is_expected.to include(/coverage config must be a regular expression/) }
end
describe '#valid?' do
subject { entry }
it { is_expected.not_to be_valid }
end
end
end
end
...@@ -4,12 +4,17 @@ describe Gitlab::Ci::Config::Entry::Global do ...@@ -4,12 +4,17 @@ describe Gitlab::Ci::Config::Entry::Global do
let(:global) { described_class.new(hash) } let(:global) { described_class.new(hash) }
describe '.nodes' do describe '.nodes' do
it 'can contain global config keys' do it 'returns a hash' do
expect(described_class.nodes).to include :before_script expect(described_class.nodes).to be_a(Hash)
end end
it 'returns a hash' do context 'when filtering all the entry/node names' do
expect(described_class.nodes).to be_a Hash it 'contains the expected node names' do
node_names = described_class.nodes.keys
expect(node_names).to match_array(%i[before_script image services
after_script variables stages
types cache coverage])
end
end end
end end
...@@ -35,7 +40,7 @@ describe Gitlab::Ci::Config::Entry::Global do ...@@ -35,7 +40,7 @@ describe Gitlab::Ci::Config::Entry::Global do
end end
it 'creates node object for each entry' do it 'creates node object for each entry' do
expect(global.descendants.count).to eq 8 expect(global.descendants.count).to eq 9
end end
it 'creates node object using valid class' do it 'creates node object using valid class' do
...@@ -176,7 +181,7 @@ describe Gitlab::Ci::Config::Entry::Global do ...@@ -176,7 +181,7 @@ describe Gitlab::Ci::Config::Entry::Global do
describe '#nodes' do describe '#nodes' do
it 'instantizes all nodes' do it 'instantizes all nodes' do
expect(global.descendants.count).to eq 8 expect(global.descendants.count).to eq 9
end end
it 'contains unspecified nodes' do it 'contains unspecified nodes' do
......
...@@ -3,6 +3,20 @@ require 'spec_helper' ...@@ -3,6 +3,20 @@ require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Job do describe Gitlab::Ci::Config::Entry::Job do
let(:entry) { described_class.new(config, name: :rspec) } let(:entry) { described_class.new(config, name: :rspec) }
describe '.nodes' do
context 'when filtering all the entry/node names' do
subject { described_class.nodes.keys }
let(:result) do
%i[before_script script stage type after_script cache
image services only except variables artifacts
environment coverage]
end
it { is_expected.to match_array result }
end
end
describe 'validations' do describe 'validations' do
before { entry.compose! } before { entry.compose! }
......
...@@ -12,11 +12,11 @@ describe Gitlab::Diff::Highlight, lib: true do ...@@ -12,11 +12,11 @@ describe Gitlab::Diff::Highlight, lib: true do
context "with a diff file" do context "with a diff file" do
let(:subject) { Gitlab::Diff::Highlight.new(diff_file, repository: project.repository).highlight } let(:subject) { Gitlab::Diff::Highlight.new(diff_file, repository: project.repository).highlight }
it 'should return Gitlab::Diff::Line elements' do it 'returns Gitlab::Diff::Line elements' do
expect(subject.first).to be_an_instance_of(Gitlab::Diff::Line) expect(subject.first).to be_an_instance_of(Gitlab::Diff::Line)
end end
it 'should not modify "match" lines' do it 'does not modify "match" lines' do
expect(subject[0].text).to eq('@@ -6,12 +6,18 @@ module Popen') expect(subject[0].text).to eq('@@ -6,12 +6,18 @@ module Popen')
expect(subject[22].text).to eq('@@ -19,6 +25,7 @@ module Popen') expect(subject[22].text).to eq('@@ -19,6 +25,7 @@ module Popen')
end end
...@@ -43,11 +43,11 @@ describe Gitlab::Diff::Highlight, lib: true do ...@@ -43,11 +43,11 @@ describe Gitlab::Diff::Highlight, lib: true do
context "with diff lines" do context "with diff lines" do
let(:subject) { Gitlab::Diff::Highlight.new(diff_file.diff_lines, repository: project.repository).highlight } let(:subject) { Gitlab::Diff::Highlight.new(diff_file.diff_lines, repository: project.repository).highlight }
it 'should return Gitlab::Diff::Line elements' do it 'returns Gitlab::Diff::Line elements' do
expect(subject.first).to be_an_instance_of(Gitlab::Diff::Line) expect(subject.first).to be_an_instance_of(Gitlab::Diff::Line)
end end
it 'should not modify "match" lines' do it 'does not modify "match" lines' do
expect(subject[0].text).to eq('@@ -6,12 +6,18 @@ module Popen') expect(subject[0].text).to eq('@@ -6,12 +6,18 @@ module Popen')
expect(subject[22].text).to eq('@@ -19,6 +25,7 @@ module Popen') expect(subject[22].text).to eq('@@ -19,6 +25,7 @@ module Popen')
end end
......
...@@ -12,7 +12,7 @@ describe Gitlab::Diff::ParallelDiff, lib: true do ...@@ -12,7 +12,7 @@ describe Gitlab::Diff::ParallelDiff, lib: true do
subject { described_class.new(diff_file) } subject { described_class.new(diff_file) }
describe '#parallelize' do describe '#parallelize' do
it 'should return an array of arrays containing the parsed diff' do it 'returns an array of arrays containing the parsed diff' do
diff_lines = diff_file.highlighted_diff_lines diff_lines = diff_file.highlighted_diff_lines
expected = [ expected = [
# Unchanged lines # Unchanged lines
......
...@@ -12,7 +12,7 @@ describe Gitlab::Highlight, lib: true do ...@@ -12,7 +12,7 @@ describe Gitlab::Highlight, lib: true do
Gitlab::Highlight.highlight_lines(project.repository, commit.id, 'files/ruby/popen.rb') Gitlab::Highlight.highlight_lines(project.repository, commit.id, 'files/ruby/popen.rb')
end end
it 'should properly highlight all the lines' do it 'highlights all the lines properly' do
expect(lines[4]).to eq(%Q{<span id="LC5" class="line"> <span class="kp">extend</span> <span class="nb">self</span></span>\n}) expect(lines[4]).to eq(%Q{<span id="LC5" class="line"> <span class="kp">extend</span> <span class="nb">self</span></span>\n})
expect(lines[21]).to eq(%Q{<span id="LC22" class="line"> <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>\n}) expect(lines[21]).to eq(%Q{<span id="LC22" class="line"> <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>\n})
expect(lines[26]).to eq(%Q{<span id="LC27" class="line"> <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span>\n}) expect(lines[26]).to eq(%Q{<span id="LC27" class="line"> <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span>\n})
......
...@@ -52,6 +52,7 @@ snippets: ...@@ -52,6 +52,7 @@ snippets:
- project - project
- notes - notes
- award_emoji - award_emoji
- user_agent_detail
releases: releases:
- project - project
project_members: project_members:
......
...@@ -92,5 +92,29 @@ describe Gitlab::ImportExport::MembersMapper, services: true do ...@@ -92,5 +92,29 @@ describe Gitlab::ImportExport::MembersMapper, services: true do
expect(members_mapper.map[exported_user_id]).to eq(user2.id) expect(members_mapper.map[exported_user_id]).to eq(user2.id)
end end
end end
context 'importer same as group member' do
let(:user2) { create(:admin, authorized_projects_populated: true) }
let(:group) { create(:group) }
let(:project) { create(:empty_project, :public, name: 'searchable_project', namespace: group) }
let(:members_mapper) do
described_class.new(
exported_members: exported_members, user: user2, project: project)
end
before do
group.add_users([user, user2], GroupMember::DEVELOPER)
end
it 'maps the project member' do
expect(members_mapper.map[exported_user_id]).to eq(user2.id)
end
it 'maps the project member if it already exists' do
project.add_master(user2)
expect(members_mapper.map[exported_user_id]).to eq(user2.id)
end
end
end end
end end
...@@ -222,6 +222,7 @@ CommitStatus: ...@@ -222,6 +222,7 @@ CommitStatus:
- queued_at - queued_at
- token - token
- lock_version - lock_version
- coverage_regex
Ci::Variable: Ci::Variable:
- id - id
- project_id - project_id
......
...@@ -14,7 +14,7 @@ describe Gitlab::LDAP::Access, lib: true do ...@@ -14,7 +14,7 @@ describe Gitlab::LDAP::Access, lib: true do
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
it 'should block user in GitLab' do it 'blocks user in GitLab' do
expect(access).to receive(:block_user).with(user, 'does not exist anymore') expect(access).to receive(:block_user).with(user, 'does not exist anymore')
access.allowed? access.allowed?
......
...@@ -221,6 +221,47 @@ describe Ci::Build, :models do ...@@ -221,6 +221,47 @@ describe Ci::Build, :models do
end end
end end
describe '#coverage_regex' do
subject { build.coverage_regex }
context 'when project has build_coverage_regex set' do
let(:project_regex) { '\(\d+\.\d+\) covered' }
before do
project.build_coverage_regex = project_regex
end
context 'and coverage_regex attribute is not set' do
it { is_expected.to eq(project_regex) }
end
context 'but coverage_regex attribute is also set' do
let(:build_regex) { 'Code coverage: \d+\.\d+' }
before do
build.coverage_regex = build_regex
end
it { is_expected.to eq(build_regex) }
end
end
context 'when neither project nor build has coverage regex set' do
it { is_expected.to be_nil }
end
end
describe '#update_coverage' do
context "regarding coverage_regex's value," do
it "saves the correct extracted coverage value" do
build.coverage_regex = '\(\d+.\d+\%\) covered'
allow(build).to receive(:trace) { 'Coverage 1033 / 1051 LOC (98.29%) covered' }
expect(build).to receive(:update_attributes).with(coverage: 98.29) { true }
expect(build.update_coverage).to be true
end
end
end
describe 'deployment' do describe 'deployment' do
describe '#last_deployment' do describe '#last_deployment' do
subject { build.last_deployment } subject { build.last_deployment }
......
...@@ -27,7 +27,6 @@ describe 'CycleAnalytics#code', feature: true do ...@@ -27,7 +27,6 @@ describe 'CycleAnalytics#code', feature: true do
context "when a regular merge request (that doesn't close the issue) is created" do context "when a regular merge request (that doesn't close the issue) is created" do
it "returns nil" do it "returns nil" do
5.times do
issue = create(:issue, project: project) issue = create(:issue, project: project)
create_commit_referencing_issue(issue) create_commit_referencing_issue(issue)
...@@ -35,7 +34,6 @@ describe 'CycleAnalytics#code', feature: true do ...@@ -35,7 +34,6 @@ describe 'CycleAnalytics#code', feature: true do
merge_merge_requests_closing_issue(issue) merge_merge_requests_closing_issue(issue)
deploy_master deploy_master
end
expect(subject[:code].median).to be_nil expect(subject[:code].median).to be_nil
end end
...@@ -60,14 +58,12 @@ describe 'CycleAnalytics#code', feature: true do ...@@ -60,14 +58,12 @@ describe 'CycleAnalytics#code', feature: true do
context "when a regular merge request (that doesn't close the issue) is created" do context "when a regular merge request (that doesn't close the issue) is created" do
it "returns nil" do it "returns nil" do
5.times do
issue = create(:issue, project: project) issue = create(:issue, project: project)
create_commit_referencing_issue(issue) create_commit_referencing_issue(issue)
create_merge_request_closing_issue(issue, message: "Closes nothing") create_merge_request_closing_issue(issue, message: "Closes nothing")
merge_merge_requests_closing_issue(issue) merge_merge_requests_closing_issue(issue)
end
expect(subject[:code].median).to be_nil expect(subject[:code].median).to be_nil
end end
......
...@@ -33,14 +33,12 @@ describe 'CycleAnalytics#issue', models: true do ...@@ -33,14 +33,12 @@ describe 'CycleAnalytics#issue', models: true do
context "when a regular label (instead of a list label) is added to the issue" do context "when a regular label (instead of a list label) is added to the issue" do
it "returns nil" do it "returns nil" do
5.times do
regular_label = create(:label) regular_label = create(:label)
issue = create(:issue, project: project) issue = create(:issue, project: project)
issue.update(label_ids: [regular_label.id]) issue.update(label_ids: [regular_label.id])
create_merge_request_closing_issue(issue) create_merge_request_closing_issue(issue)
merge_merge_requests_closing_issue(issue) merge_merge_requests_closing_issue(issue)
end
expect(subject[:issue].median).to be_nil expect(subject[:issue].median).to be_nil
end end
......
...@@ -29,11 +29,9 @@ describe 'CycleAnalytics#production', feature: true do ...@@ -29,11 +29,9 @@ describe 'CycleAnalytics#production', feature: true do
context "when a regular merge request (that doesn't close the issue) is merged and deployed" do context "when a regular merge request (that doesn't close the issue) is merged and deployed" do
it "returns nil" do it "returns nil" do
5.times do
merge_request = create(:merge_request) merge_request = create(:merge_request)
MergeRequests::MergeService.new(project, user).execute(merge_request) MergeRequests::MergeService.new(project, user).execute(merge_request)
deploy_master deploy_master
end
expect(subject[:production].median).to be_nil expect(subject[:production].median).to be_nil
end end
...@@ -41,12 +39,10 @@ describe 'CycleAnalytics#production', feature: true do ...@@ -41,12 +39,10 @@ describe 'CycleAnalytics#production', feature: true do
context "when the deployment happens to a non-production environment" do context "when the deployment happens to a non-production environment" do
it "returns nil" do it "returns nil" do
5.times do
issue = create(:issue, project: project) issue = create(:issue, project: project)
merge_request = create_merge_request_closing_issue(issue) merge_request = create_merge_request_closing_issue(issue)
MergeRequests::MergeService.new(project, user).execute(merge_request) MergeRequests::MergeService.new(project, user).execute(merge_request)
deploy_master(environment: 'staging') deploy_master(environment: 'staging')
end
expect(subject[:production].median).to be_nil expect(subject[:production].median).to be_nil
end end
......
...@@ -23,9 +23,7 @@ describe 'CycleAnalytics#review', feature: true do ...@@ -23,9 +23,7 @@ describe 'CycleAnalytics#review', feature: true do
context "when a regular merge request (that doesn't close the issue) is created and merged" do context "when a regular merge request (that doesn't close the issue) is created and merged" do
it "returns nil" do it "returns nil" do
5.times do
MergeRequests::MergeService.new(project, user).execute(create(:merge_request)) MergeRequests::MergeService.new(project, user).execute(create(:merge_request))
end
expect(subject[:review].median).to be_nil expect(subject[:review].median).to be_nil
end end
......
...@@ -40,11 +40,9 @@ describe 'CycleAnalytics#staging', feature: true do ...@@ -40,11 +40,9 @@ describe 'CycleAnalytics#staging', feature: true do
context "when a regular merge request (that doesn't close the issue) is merged and deployed" do context "when a regular merge request (that doesn't close the issue) is merged and deployed" do
it "returns nil" do it "returns nil" do
5.times do
merge_request = create(:merge_request) merge_request = create(:merge_request)
MergeRequests::MergeService.new(project, user).execute(merge_request) MergeRequests::MergeService.new(project, user).execute(merge_request)
deploy_master deploy_master
end
expect(subject[:staging].median).to be_nil expect(subject[:staging].median).to be_nil
end end
...@@ -52,12 +50,10 @@ describe 'CycleAnalytics#staging', feature: true do ...@@ -52,12 +50,10 @@ describe 'CycleAnalytics#staging', feature: true do
context "when the deployment happens to a non-production environment" do context "when the deployment happens to a non-production environment" do
it "returns nil" do it "returns nil" do
5.times do
issue = create(:issue, project: project) issue = create(:issue, project: project)
merge_request = create_merge_request_closing_issue(issue) merge_request = create_merge_request_closing_issue(issue)
MergeRequests::MergeService.new(project, user).execute(merge_request) MergeRequests::MergeService.new(project, user).execute(merge_request)
deploy_master(environment: 'staging') deploy_master(environment: 'staging')
end
expect(subject[:staging].median).to be_nil expect(subject[:staging].median).to be_nil
end end
......
...@@ -24,7 +24,6 @@ describe 'CycleAnalytics#test', feature: true do ...@@ -24,7 +24,6 @@ describe 'CycleAnalytics#test', feature: true do
context "when the pipeline is for a regular merge request (that doesn't close an issue)" do context "when the pipeline is for a regular merge request (that doesn't close an issue)" do
it "returns nil" do it "returns nil" do
5.times do
issue = create(:issue, project: project) issue = create(:issue, project: project)
merge_request = create_merge_request_closing_issue(issue) merge_request = create_merge_request_closing_issue(issue)
pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha) pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
...@@ -33,7 +32,6 @@ describe 'CycleAnalytics#test', feature: true do ...@@ -33,7 +32,6 @@ describe 'CycleAnalytics#test', feature: true do
pipeline.succeed! pipeline.succeed!
merge_merge_requests_closing_issue(issue) merge_merge_requests_closing_issue(issue)
end
expect(subject[:test].median).to be_nil expect(subject[:test].median).to be_nil
end end
...@@ -41,12 +39,10 @@ describe 'CycleAnalytics#test', feature: true do ...@@ -41,12 +39,10 @@ describe 'CycleAnalytics#test', feature: true do
context "when the pipeline is not for a merge request" do context "when the pipeline is not for a merge request" do
it "returns nil" do it "returns nil" do
5.times do
pipeline = create(:ci_pipeline, ref: "refs/heads/master", sha: project.repository.commit('master').sha) pipeline = create(:ci_pipeline, ref: "refs/heads/master", sha: project.repository.commit('master').sha)
pipeline.run! pipeline.run!
pipeline.succeed! pipeline.succeed!
end
expect(subject[:test].median).to be_nil expect(subject[:test].median).to be_nil
end end
...@@ -54,7 +50,6 @@ describe 'CycleAnalytics#test', feature: true do ...@@ -54,7 +50,6 @@ describe 'CycleAnalytics#test', feature: true do
context "when the pipeline is dropped (failed)" do context "when the pipeline is dropped (failed)" do
it "returns nil" do it "returns nil" do
5.times do
issue = create(:issue, project: project) issue = create(:issue, project: project)
merge_request = create_merge_request_closing_issue(issue) merge_request = create_merge_request_closing_issue(issue)
pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha) pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
...@@ -63,7 +58,6 @@ describe 'CycleAnalytics#test', feature: true do ...@@ -63,7 +58,6 @@ describe 'CycleAnalytics#test', feature: true do
pipeline.drop! pipeline.drop!
merge_merge_requests_closing_issue(issue) merge_merge_requests_closing_issue(issue)
end
expect(subject[:test].median).to be_nil expect(subject[:test].median).to be_nil
end end
...@@ -71,7 +65,6 @@ describe 'CycleAnalytics#test', feature: true do ...@@ -71,7 +65,6 @@ describe 'CycleAnalytics#test', feature: true do
context "when the pipeline is cancelled" do context "when the pipeline is cancelled" do
it "returns nil" do it "returns nil" do
5.times do
issue = create(:issue, project: project) issue = create(:issue, project: project)
merge_request = create_merge_request_closing_issue(issue) merge_request = create_merge_request_closing_issue(issue)
pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha) pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
...@@ -80,7 +73,6 @@ describe 'CycleAnalytics#test', feature: true do ...@@ -80,7 +73,6 @@ describe 'CycleAnalytics#test', feature: true do
pipeline.cancel! pipeline.cancel!
merge_merge_requests_closing_issue(issue) merge_merge_requests_closing_issue(issue)
end
expect(subject[:test].median).to be_nil expect(subject[:test].median).to be_nil
end end
......
...@@ -117,7 +117,7 @@ describe ProjectMember, models: true do ...@@ -117,7 +117,7 @@ describe ProjectMember, models: true do
users = create_list(:user, 2) users = create_list(:user, 2)
described_class.add_users_to_projects( described_class.add_users_to_projects(
[projects.first.id, projects.second], [projects.first.id, projects.second.id],
[users.first.id, users.second], [users.first.id, users.second],
described_class::MASTER) described_class::MASTER)
......
...@@ -67,7 +67,7 @@ describe API::Builds, api: true do ...@@ -67,7 +67,7 @@ describe API::Builds, api: true do
context 'unauthorized user' do context 'unauthorized user' do
let(:api_user) { nil } let(:api_user) { nil }
it 'should not return project builds' do it 'does not return project builds' do
expect(response).to have_http_status(401) expect(response).to have_http_status(401)
end end
end end
......
...@@ -326,7 +326,7 @@ describe API::Groups, api: true do ...@@ -326,7 +326,7 @@ describe API::Groups, api: true do
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
it "should only return projects to which user has access" do it "only returns projects to which user has access" do
project3.team << [user3, :developer] project3.team << [user3, :developer]
get api("/groups/#{group1.id}/projects", user3) get api("/groups/#{group1.id}/projects", user3)
...@@ -338,7 +338,7 @@ describe API::Groups, api: true do ...@@ -338,7 +338,7 @@ describe API::Groups, api: true do
end end
context "when authenticated as admin" do context "when authenticated as admin" do
it "should return any existing group" do it "returns any existing group" do
get api("/groups/#{group2.id}/projects", admin) get api("/groups/#{group2.id}/projects", admin)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
...@@ -346,7 +346,7 @@ describe API::Groups, api: true do ...@@ -346,7 +346,7 @@ describe API::Groups, api: true do
expect(json_response.first['name']).to eq(project2.name) expect(json_response.first['name']).to eq(project2.name)
end end
it "should not return a non existing group" do it "does not return a non existing group" do
get api("/groups/1328/projects", admin) get api("/groups/1328/projects", admin)
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
...@@ -354,7 +354,7 @@ describe API::Groups, api: true do ...@@ -354,7 +354,7 @@ describe API::Groups, api: true do
end end
context 'when using group path in URL' do context 'when using group path in URL' do
it 'should return any existing group' do it 'returns any existing group' do
get api("/groups/#{group1.path}/projects", admin) get api("/groups/#{group1.path}/projects", admin)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
......
...@@ -4,6 +4,7 @@ describe API::ProjectSnippets, api: true do ...@@ -4,6 +4,7 @@ describe API::ProjectSnippets, api: true do
include ApiHelpers include ApiHelpers
let(:project) { create(:empty_project, :public) } let(:project) { create(:empty_project, :public) }
let(:user) { create(:user) }
let(:admin) { create(:admin) } let(:admin) { create(:admin) }
describe 'GET /projects/:project_id/snippets/:id' do describe 'GET /projects/:project_id/snippets/:id' do
...@@ -22,7 +23,7 @@ describe API::ProjectSnippets, api: true do ...@@ -22,7 +23,7 @@ describe API::ProjectSnippets, api: true do
let(:user) { create(:user) } let(:user) { create(:user) }
it 'returns all snippets available to team member' do it 'returns all snippets available to team member' do
project.team << [user, :developer] project.add_developer(user)
public_snippet = create(:project_snippet, :public, project: project) public_snippet = create(:project_snippet, :public, project: project)
internal_snippet = create(:project_snippet, :internal, project: project) internal_snippet = create(:project_snippet, :internal, project: project)
private_snippet = create(:project_snippet, :private, project: project) private_snippet = create(:project_snippet, :private, project: project)
...@@ -50,7 +51,7 @@ describe API::ProjectSnippets, api: true do ...@@ -50,7 +51,7 @@ describe API::ProjectSnippets, api: true do
title: 'Test Title', title: 'Test Title',
file_name: 'test.rb', file_name: 'test.rb',
code: 'puts "hello world"', code: 'puts "hello world"',
visibility_level: Gitlab::VisibilityLevel::PUBLIC visibility_level: Snippet::PUBLIC
} }
end end
...@@ -72,6 +73,51 @@ describe API::ProjectSnippets, api: true do ...@@ -72,6 +73,51 @@ describe API::ProjectSnippets, api: true do
expect(response).to have_http_status(400) expect(response).to have_http_status(400)
end end
context 'when the snippet is spam' do
def create_snippet(project, snippet_params = {})
project.add_developer(user)
post api("/projects/#{project.id}/snippets", user), params.merge(snippet_params)
end
before do
allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
end
context 'when the project is private' do
let(:private_project) { create(:project_empty_repo, :private) }
context 'when the snippet is public' do
it 'creates the snippet' do
expect { create_snippet(private_project, visibility_level: Snippet::PUBLIC) }.
to change { Snippet.count }.by(1)
end
end
end
context 'when the project is public' do
context 'when the snippet is private' do
it 'creates the snippet' do
expect { create_snippet(project, visibility_level: Snippet::PRIVATE) }.
to change { Snippet.count }.by(1)
end
end
context 'when the snippet is public' do
it 'rejects the shippet' do
expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
not_to change { Snippet.count }
expect(response).to have_http_status(400)
end
it 'creates a spam log' do
expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
to change { SpamLog.count }.by(1)
end
end
end
end
end end
describe 'PUT /projects/:project_id/snippets/:id/' do describe 'PUT /projects/:project_id/snippets/:id/' do
......
...@@ -459,7 +459,7 @@ describe API::Projects, api: true do ...@@ -459,7 +459,7 @@ describe API::Projects, api: true do
before { project } before { project }
before { admin } before { admin }
it 'should create new project without path and return 201' do it 'creates new project without path and return 201' do
expect { post api("/projects/user/#{user.id}", admin), name: 'foo' }.to change {Project.count}.by(1) expect { post api("/projects/user/#{user.id}", admin), name: 'foo' }.to change {Project.count}.by(1)
expect(response).to have_http_status(201) expect(response).to have_http_status(201)
end end
......
...@@ -80,7 +80,7 @@ describe API::Snippets, api: true do ...@@ -80,7 +80,7 @@ describe API::Snippets, api: true do
title: 'Test Title', title: 'Test Title',
file_name: 'test.rb', file_name: 'test.rb',
content: 'puts "hello world"', content: 'puts "hello world"',
visibility_level: Gitlab::VisibilityLevel::PUBLIC visibility_level: Snippet::PUBLIC
} }
end end
...@@ -101,6 +101,36 @@ describe API::Snippets, api: true do ...@@ -101,6 +101,36 @@ describe API::Snippets, api: true do
expect(response).to have_http_status(400) expect(response).to have_http_status(400)
end end
context 'when the snippet is spam' do
def create_snippet(snippet_params = {})
post api('/snippets', user), params.merge(snippet_params)
end
before do
allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
end
context 'when the snippet is private' do
it 'creates the snippet' do
expect { create_snippet(visibility_level: Snippet::PRIVATE) }.
to change { Snippet.count }.by(1)
end
end
context 'when the snippet is public' do
it 'rejects the shippet' do
expect { create_snippet(visibility_level: Snippet::PUBLIC) }.
not_to change { Snippet.count }
expect(response).to have_http_status(400)
end
it 'creates a spam log' do
expect { create_snippet(visibility_level: Snippet::PUBLIC) }.
to change { SpamLog.count }.by(1)
end
end
end
end end
describe 'PUT /snippets/:id' do describe 'PUT /snippets/:id' do
......
...@@ -458,7 +458,7 @@ describe Ci::API::Builds do ...@@ -458,7 +458,7 @@ describe Ci::API::Builds do
before { build.run! } before { build.run! }
describe "POST /builds/:id/artifacts/authorize" do describe "POST /builds/:id/artifacts/authorize" do
context "should authorize posting artifact to running build" do context "authorizes posting artifact to running build" do
it "using token as parameter" do it "using token as parameter" do
post authorize_url, { token: build.token }, headers post authorize_url, { token: build.token }, headers
...@@ -492,7 +492,7 @@ describe Ci::API::Builds do ...@@ -492,7 +492,7 @@ describe Ci::API::Builds do
end end
end end
context "should fail to post too large artifact" do context "fails to post too large artifact" do
it "using token as parameter" do it "using token as parameter" do
stub_application_setting(max_artifacts_size: 0) stub_application_setting(max_artifacts_size: 0)
......
...@@ -9,7 +9,7 @@ describe EventCreateService, services: true do ...@@ -9,7 +9,7 @@ describe EventCreateService, services: true do
it { expect(service.open_issue(issue, issue.author)).to be_truthy } it { expect(service.open_issue(issue, issue.author)).to be_truthy }
it "should create new event" do it "creates new event" do
expect { service.open_issue(issue, issue.author) }.to change { Event.count } expect { service.open_issue(issue, issue.author) }.to change { Event.count }
end end
end end
...@@ -19,7 +19,7 @@ describe EventCreateService, services: true do ...@@ -19,7 +19,7 @@ describe EventCreateService, services: true do
it { expect(service.close_issue(issue, issue.author)).to be_truthy } it { expect(service.close_issue(issue, issue.author)).to be_truthy }
it "should create new event" do it "creates new event" do
expect { service.close_issue(issue, issue.author) }.to change { Event.count } expect { service.close_issue(issue, issue.author) }.to change { Event.count }
end end
end end
...@@ -29,7 +29,7 @@ describe EventCreateService, services: true do ...@@ -29,7 +29,7 @@ describe EventCreateService, services: true do
it { expect(service.reopen_issue(issue, issue.author)).to be_truthy } it { expect(service.reopen_issue(issue, issue.author)).to be_truthy }
it "should create new event" do it "creates new event" do
expect { service.reopen_issue(issue, issue.author) }.to change { Event.count } expect { service.reopen_issue(issue, issue.author) }.to change { Event.count }
end end
end end
......
...@@ -29,7 +29,7 @@ describe MergeRequests::CloseService, services: true do ...@@ -29,7 +29,7 @@ describe MergeRequests::CloseService, services: true do
it { expect(@merge_request).to be_valid } it { expect(@merge_request).to be_valid }
it { expect(@merge_request).to be_closed } it { expect(@merge_request).to be_closed }
it 'should execute hooks with close action' do it 'executes hooks with close action' do
expect(service).to have_received(:execute_hooks). expect(service).to have_received(:execute_hooks).
with(@merge_request, 'close') with(@merge_request, 'close')
end end
......
...@@ -63,7 +63,6 @@ module CycleAnalyticsHelpers ...@@ -63,7 +63,6 @@ module CycleAnalyticsHelpers
# test case. # test case.
allow(self).to receive(:project) { other_project } allow(self).to receive(:project) { other_project }
5.times do
data = data_fn[self] data = data_fn[self]
start_time = Time.now start_time = Time.now
end_time = rand(1..10).days.from_now end_time = rand(1..10).days.from_now
...@@ -77,7 +76,6 @@ module CycleAnalyticsHelpers ...@@ -77,7 +76,6 @@ module CycleAnalyticsHelpers
end end
Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn
end
# Turn off the stub before checking assertions # Turn off the stub before checking assertions
allow(self).to receive(:project).and_call_original allow(self).to receive(:project).and_call_original
...@@ -114,7 +112,6 @@ module CycleAnalyticsHelpers ...@@ -114,7 +112,6 @@ module CycleAnalyticsHelpers
context "start condition NOT PRESENT: #{start_time_conditions.map(&:first).to_sentence}" do context "start condition NOT PRESENT: #{start_time_conditions.map(&:first).to_sentence}" do
context "end condition: #{end_time_conditions.map(&:first).to_sentence}" do context "end condition: #{end_time_conditions.map(&:first).to_sentence}" do
it "returns nil" do it "returns nil" do
5.times do
data = data_fn[self] data = data_fn[self]
end_time = rand(1..10).days.from_now end_time = rand(1..10).days.from_now
...@@ -123,7 +120,6 @@ module CycleAnalyticsHelpers ...@@ -123,7 +120,6 @@ module CycleAnalyticsHelpers
end end
Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn
end
expect(subject[phase].median).to be_nil expect(subject[phase].median).to be_nil
end end
...@@ -133,7 +129,6 @@ module CycleAnalyticsHelpers ...@@ -133,7 +129,6 @@ module CycleAnalyticsHelpers
context "start condition: #{start_time_conditions.map(&:first).to_sentence}" do context "start condition: #{start_time_conditions.map(&:first).to_sentence}" do
context "end condition NOT PRESENT: #{end_time_conditions.map(&:first).to_sentence}" do context "end condition NOT PRESENT: #{end_time_conditions.map(&:first).to_sentence}" do
it "returns nil" do it "returns nil" do
5.times do
data = data_fn[self] data = data_fn[self]
start_time = Time.now start_time = Time.now
...@@ -142,7 +137,6 @@ module CycleAnalyticsHelpers ...@@ -142,7 +137,6 @@ module CycleAnalyticsHelpers
end end
post_fn[self, data] if post_fn post_fn[self, data] if post_fn
end
expect(subject[phase].median).to be_nil expect(subject[phase].median).to be_nil
end end
......
...@@ -20,7 +20,7 @@ describe 'gitlab:mail_google_schema_whitelisting rake task' do ...@@ -20,7 +20,7 @@ describe 'gitlab:mail_google_schema_whitelisting rake task' do
Rake.application.invoke_task "gitlab:mail_google_schema_whitelisting" Rake.application.invoke_task "gitlab:mail_google_schema_whitelisting"
end end
it 'should run the task without errors' do it 'runs the task without errors' do
expect { run_rake_task }.not_to raise_error expect { run_rake_task }.not_to raise_error
end end
end end
......
...@@ -8,14 +8,14 @@ describe ProjectDestroyWorker do ...@@ -8,14 +8,14 @@ describe ProjectDestroyWorker do
describe "#perform" do describe "#perform" do
it "deletes the project" do it "deletes the project" do
subject.perform(project.id, project.owner, {}) subject.perform(project.id, project.owner.id, {})
expect(Project.all).not_to include(project) expect(Project.all).not_to include(project)
expect(Dir.exist?(path)).to be_falsey expect(Dir.exist?(path)).to be_falsey
end end
it "deletes the project but skips repo deletion" do it "deletes the project but skips repo deletion" do
subject.perform(project.id, project.owner, { "skip_repo" => true }) subject.perform(project.id, project.owner.id, { "skip_repo" => true })
expect(Project.all).not_to include(project) expect(Project.all).not_to include(project)
expect(Dir.exist?(path)).to be_truthy expect(Dir.exist?(path)).to be_truthy
......
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