Commit 35f9f79b authored by Ruben Davila's avatar Ruben Davila

Merge branch 'master' into 8-11-stable

parents 69ed2c96 06e3bb9f
......@@ -15,6 +15,7 @@ variables:
USE_DB: "true"
USE_BUNDLE_INSTALL: "true"
GIT_DEPTH: "20"
PHANTOMJS_VERSION: "2.1.1"
before_script:
- source ./scripts/prepare_build.sh
......
......@@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.11.0 (unreleased)
- Remove the http_parser.rb dependency by removing the tinder gem. !5758 (tbalthazar)
- Ability to specify branches for Pivotal Tracker integration (Egor Lynko)
- Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres)
- Fix rename `add_users_into_project` and `projects_ids`. !20512 (herminiotorres)
- Fix the title of the toggle dropdown button. !5515 (herminiotorres)
......@@ -32,12 +33,14 @@ v 8.11.0 (unreleased)
- Add "No one can push" as an option for protected branches. !5081
- Improve performance of AutolinkFilter#text_parse by using XPath
- Add experimental Redis Sentinel support !1877
- Rendering of SVGs as blobs is now limited to SVGs with a size smaller or equal to 2MB
- Fix branches page dropdown sort initial state (ClemMakesApps)
- Environments have an url to link to
- Various redundant database indexes have been removed
- Update `timeago` plugin to use multiple string/locale settings
- Remove unused images (ClemMakesApps)
- Limit git rev-list output count to one in forced push check
- Show deployment status on merge requests with external URLs
- Clean up unused routes (Josef Strzibny)
- Fix issue on empty project to allow developers to only push to protected branches if given permission
- Add green outline to New Branch button. !5447 (winniehell)
......@@ -54,6 +57,7 @@ v 8.11.0 (unreleased)
- Optimize checking if a user has read access to a list of issues !5370
- Store all DB secrets in secrets.yml, under descriptive names !5274
- Nokogiri's various parsing methods are now instrumented
- Add archived badge to project list !5798
- Add simple identifier to public SSH keys (muteor)
- Admin page now references docs instead of a specific file !5600 (AnAverageHuman)
- Add a way to send an email and create an issue based on private personal token. Find the email address from issues page. !3363
......@@ -93,6 +97,7 @@ v 8.11.0 (unreleased)
- Bump gitlab_git to lazy load compare commits
- Reduce number of queries made for merge_requests/:id/diffs
- Sensible state specific default sort order for issues and merge requests !5453 (tomb0y)
- Fix bug where destroying a namespace would not always destroy projects
- Fix RequestProfiler::Middleware error when code is reloaded in development
- Catch what warden might throw when profiling requests to re-throw it
- Avoid commit lookup on diff_helper passing existing local variable to the helper method
......@@ -106,6 +111,8 @@ v 8.11.0 (unreleased)
- Sort folders with submodules in Files view !5521
- Each `File::exists?` replaced to `File::exist?` because of deprecate since ruby version 2.2.0
- Add auto-completition in pipeline (Katarzyna Kobierska Ula Budziszewska)
- Fix a memory leak caused by Banzai::Filter::SanitizationFilter
- Speed up todos queries by limiting the projects set we join with
v 8.10.5
- Add a data migration to fix some missing timestamps in the members table. !5670
......@@ -270,6 +277,7 @@ v 8.10.0
- Fix new snippet style bug (elliotec)
- Instrument Rinku usage
- Be explicit to define merge request discussion variables
- Use cache for todos counter calling TodoService
- Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab
- RailsCache metris now includes fetch_hit/fetch_miss and read_hit/read_miss info.
- Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w)
......
......@@ -5,13 +5,10 @@
this.Issuable = {
init: function() {
if (!issuable_created) {
issuable_created = true;
Issuable.initTemplates();
Issuable.initSearch();
Issuable.initChecks();
return Issuable.initLabelFilterRemove();
}
},
initTemplates: function() {
return Issuable.labelRow = _.template('<% _.each(labels, function(label){ %> <span class="label-row btn-group" role="group" aria-label="<%- label.title %>" style="color: <%- label.text_color %>;"> <a href="#" class="btn btn-transparent has-tooltip" style="background-color: <%- label.color %>;" title="<%- label.description %>" data-container="body"> <%- label.title %> </a> <button type="button" class="btn btn-transparent label-remove js-label-filter-remove" style="background-color: <%- label.color %>;" data-label="<%- label.title %>"> <i class="fa fa-times"></i> </button> </span> <% }); %>');
......
......@@ -69,6 +69,10 @@
&.ci-success {
color: $gl-success;
a.environment {
color: inherit;
}
}
&.ci-success_with_warnings {
......@@ -126,7 +130,6 @@
&.has-conflicts .fa-exclamation-triangle {
color: $gl-warning;
}
}
p:last-child {
......
......@@ -48,9 +48,9 @@ class Admin::GroupsController < Admin::ApplicationController
end
def destroy
DestroyGroupService.new(@group, current_user).execute
DestroyGroupService.new(@group, current_user).async_execute
redirect_to admin_groups_path, notice: 'Group was successfully deleted.'
redirect_to admin_groups_path, alert: "Group '#{@group.name}' was scheduled for deletion."
end
private
......
......@@ -37,8 +37,8 @@ class Dashboard::TodosController < Dashboard::ApplicationController
def todos_counts
{
count: TodosFinder.new(current_user, state: :pending).execute.count,
done_count: TodosFinder.new(current_user, state: :done).execute.count
count: current_user.todos_pending_count,
done_count: current_user.todos_done_count
}
end
end
......@@ -87,9 +87,9 @@ class GroupsController < Groups::ApplicationController
end
def destroy
DestroyGroupService.new(@group, current_user).execute
DestroyGroupService.new(@group, current_user).async_execute
redirect_to root_path, alert: "Group '#{@group.name}' was successfully deleted."
redirect_to root_path, alert: "Group '#{@group.name}' was scheduled for deletion."
end
protected
......
class ProjectsFinder < UnionFinder
def execute(current_user = nil, options = {})
def execute(current_user = nil, project_ids_relation = nil)
segments = all_projects(current_user)
segments.map! { |s| s.where(id: project_ids_relation) } if project_ids_relation
find_union(segments, Project)
end
......
......@@ -27,9 +27,11 @@ class TodosFinder
items = by_action_id(items)
items = by_action(items)
items = by_author(items)
items = by_project(items)
items = by_state(items)
items = by_type(items)
# Filtering by project HAS TO be the last because we use
# the project IDs yielded by the todos query thus far
items = by_project(items)
items.reorder(id: :desc)
end
......@@ -91,14 +93,9 @@ class TodosFinder
@project
end
def projects
return @projects if defined?(@projects)
if project?
@projects = project
else
@projects = ProjectsFinder.new.execute(current_user)
end
def projects(items)
item_project_ids = items.reorder(nil).select(:project_id)
ProjectsFinder.new.execute(current_user, item_project_ids)
end
def type?
......@@ -136,8 +133,9 @@ class TodosFinder
def by_project(items)
if project?
items = items.where(project: project)
elsif projects
items = items.merge(projects).joins(:project)
else
item_projects = projects(items)
items = items.merge(item_projects).joins(:project)
end
items
......
module TodosHelper
def todos_pending_count
@todos_pending_count ||= TodosFinder.new(current_user, state: :pending).execute.count
@todos_pending_count ||= current_user.todos_pending_count
end
def todos_done_count
@todos_done_count ||= TodosFinder.new(current_user, state: :done).execute.count
@todos_done_count ||= current_user.todos_done_count
end
def todo_action_name(todo)
......
......@@ -3,6 +3,9 @@ class Blob < SimpleDelegator
CACHE_TIME = 60 # Cache raw blobs referred to by a (mutable) ref for 1 minute
CACHE_TIME_IMMUTABLE = 3600 # Cache blobs referred to by an immutable reference for 1 hour
# The maximum size of an SVG that can be displayed.
MAXIMUM_SVG_SIZE = 2.megabytes
# Wrap a Gitlab::Git::Blob object, or return nil when given nil
#
# This method prevents the decorated object from evaluating to "truthy" when
......@@ -31,6 +34,10 @@ class Blob < SimpleDelegator
text? && language && language.name == 'SVG'
end
def size_within_svg_limits?
size <= MAXIMUM_SVG_SIZE
end
def video?
UploaderHelper::VIDEO_EXT.include?(extname.downcase.delete('.'))
end
......
......@@ -36,4 +36,10 @@ class Deployment < ActiveRecord::Base
def manual_actions
deployable.try(:other_actions)
end
def includes_commit?(commit)
return false unless commit
project.repository.is_ancestor?(commit.id, sha)
end
end
......@@ -25,4 +25,10 @@ class Environment < ActiveRecord::Base
def nullify_external_url
self.external_url = nil if self.external_url.blank?
end
def includes_commit?(commit)
return false unless last_deployment
last_deployment.includes_commit?(commit)
end
end
......@@ -591,6 +591,14 @@ class MergeRequest < ActiveRecord::Base
!pipeline || pipeline.success?
end
def environments
return unless diff_head_commit
target_project.environments.select do |environment|
environment.includes_commit?(diff_head_commit)
end
end
def state_human_name
if merged?
"Merged"
......
class Namespace < ActiveRecord::Base
acts_as_paranoid
include Sortable
include Gitlab::ShellAdapter
......
class PivotaltrackerService < Service
include HTTParty
prop_accessor :token
API_ENDPOINT = 'https://www.pivotaltracker.com/services/v5/source_commits'
prop_accessor :token, :restrict_to_branch
validates :token, presence: true, if: :activated?
def title
......@@ -18,7 +20,17 @@ class PivotaltrackerService < Service
def fields
[
{ type: 'text', name: 'token', placeholder: '' }
{
type: 'text',
name: 'token',
placeholder: 'Pivotal Tracker API token.'
},
{
type: 'text',
name: 'restrict_to_branch',
placeholder: 'Comma-separated list of branches which will be ' \
'automatically inspected. Leave blank to include all branches.'
}
]
end
......@@ -28,8 +40,8 @@ class PivotaltrackerService < Service
def execute(data)
return unless supported_events.include?(data[:object_kind])
return unless allowed_branch?(data[:ref])
url = 'https://www.pivotaltracker.com/services/v5/source_commits'
data[:commits].each do |commit|
message = {
'source_commit' => {
......@@ -40,7 +52,7 @@ class PivotaltrackerService < Service
}
}
PivotaltrackerService.post(
url,
API_ENDPOINT,
body: message.to_json,
headers: {
'Content-Type' => 'application/json',
......@@ -49,4 +61,15 @@ class PivotaltrackerService < Service
)
end
end
private
def allowed_branch?(ref)
return true unless ref.present? && restrict_to_branch.present?
branch = Gitlab::Git.ref_name(ref)
allowed_branches = restrict_to_branch.split(',').map(&:strip)
branch.present? && allowed_branches.include?(branch)
end
end
......@@ -809,13 +809,13 @@ class User < ActiveRecord::Base
def todos_done_count(force: false)
Rails.cache.fetch(['users', id, 'todos_done_count'], force: force) do
todos.done.count
TodosFinder.new(self, state: :done).execute.count
end
end
def todos_pending_count(force: false)
Rails.cache.fetch(['users', id, 'todos_pending_count'], force: force) do
todos.pending.count
TodosFinder.new(self, state: :pending).execute.count
end
end
......
......@@ -21,6 +21,11 @@ class DeleteUserService
::Projects::DestroyService.new(project, current_user, skip_repo: true).async_execute
end
user.destroy
# Destroy the namespace after destroying the user since certain methods may depend on the namespace existing
namespace = user.namespace
user_data = user.destroy
namespace.really_destroy!
user_data
end
end
......@@ -5,13 +5,23 @@ class DestroyGroupService
@group, @current_user = group, user
end
def async_execute
group.transaction do
# Soft delete via paranoia gem
group.destroy
job_id = GroupDestroyWorker.perform_async(group.id, current_user.id)
Rails.logger.info("User #{current_user.id} scheduled a deletion of group ID #{group.id} with job ID #{job_id}")
end
end
def execute
group.projects.each do |project|
# Execute the destruction of the models immediately to ensure atomic cleanup.
# Skip repository removal because we remove directory with namespace
# that contain all this repositories
::Projects::DestroyService.new(project, current_user, skip_repo: true).async_execute
# that contain all these repositories
::Projects::DestroyService.new(project, current_user, skip_repo: true).execute
end
group.destroy
group.really_destroy!
end
end
......@@ -30,10 +30,21 @@ module MergeRequests
end
def get_branches(changes)
return [] if project.empty_repo?
return [] unless project.merge_requests_enabled
changes_list = Gitlab::ChangesList.new(changes)
changes_list.map do |change|
next unless Gitlab::Git.branch_ref?(change[:ref])
Gitlab::Git.branch_name(change[:ref])
# Deleted branch
next if Gitlab::Git.blank_ref?(change[:newrev])
# Default branch
branch_name = Gitlab::Git.branch_name(change[:ref])
next if branch_name == project.default_branch
branch_name
end.compact
end
......
......@@ -144,8 +144,9 @@ class TodoService
def mark_todos_as_done(todos, current_user)
todos = current_user.todos.where(id: todos.map(&:id)) unless todos.respond_to?(:update_all)
todos.update_all(state: :done)
marked_todos = todos.update_all(state: :done)
current_user.update_todos_count_cache
marked_todos
end
# When user marks an issue as todo
......
.file-content.image_file
- if blob.svg?
- if blob.size_within_svg_limits?
- # We need to scrub SVG but we cannot do so in the RawController: it would
- # be wrong/strange if RawController modified the data.
- blob.load_all_data!(@repository)
- blob = sanitize_svg(blob)
%img{src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}"}
- else
.nothing-here-block
The SVG could not be displayed as it is too large, you can
#{link_to('view the raw file', namespace_project_raw_path(@project.namespace, @project, @id), target: '_blank')}
instead.
- else
%img{src: namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, blob.path))}
......@@ -42,3 +42,16 @@
.ci_widget.ci-error{style: "display:none"}
= icon("times-circle")
Could not connect to the CI server. Please check your settings and try again.
- @merge_request.environments.each do |environment|
.mr-widget-heading
.ci_widget.ci-success
= ci_icon_for_status("success")
%span.hidden-sm
Deployed to
= succeed '.' do
= link_to environment.name, namespace_project_environment_path(@project.namespace, @project, environment), class: 'environment'
- external_url = environment.external_url
- if external_url
= link_to external_url, target: '_blank' do
= icon('external-link', text: "View on #{external_url.gsub(/\A.*?:\/\//, '')}", right: true)
......@@ -12,6 +12,8 @@
%li.project-row{ class: css_class }
= cache(cache_key) do
.controls
- if project.archived
%span.label.label-warning archived
- if project.commit.try(:status)
%span
= render_commit_status(project.commit)
......
class GroupDestroyWorker
include Sidekiq::Worker
sidekiq_options queue: :default
def perform(group_id, user_id)
begin
group = Group.with_deleted.find(group_id)
rescue ActiveRecord::RecordNotFound
return
end
user = User.find(user_id)
DestroyGroupService.new(group, user).execute
end
end
# rubocop:disable all
class FixNamespaces < ActiveRecord::Migration
DOWNTIME = false
def up
Namespace.where('name <> path and type is null').each do |namespace|
namespace.update_attribute(:name, namespace.path)
namespaces = exec_query('SELECT id, path FROM namespaces WHERE name <> path and type is null')
namespaces.each do |row|
id = row['id']
path = row['path']
exec_query("UPDATE namespaces SET name = '#{path}' WHERE id = #{id}")
end
end
......
class AddDeletedAtToNamespaces < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def change
add_column :namespaces, :deleted_at, :datetime
add_concurrent_index :namespaces, :deleted_at
end
end
......@@ -589,12 +589,12 @@ ActiveRecord::Schema.define(version: 20160810142633) do
t.datetime "locked_at"
t.integer "updated_by_id"
t.string "merge_error"
t.text "merge_params"
t.boolean "merge_when_build_succeeds", default: false, null: false
t.integer "merge_user_id"
t.string "merge_commit_sha"
t.datetime "deleted_at"
t.string "in_progress_merge_commit_sha"
t.text "merge_params"
end
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
......@@ -640,9 +640,11 @@ ActiveRecord::Schema.define(version: 20160810142633) do
t.boolean "share_with_group_lock", default: false
t.integer "visibility_level", default: 20, null: false
t.boolean "request_access_enabled", default: true, null: false
t.datetime "deleted_at"
end
add_index "namespaces", ["created_at"], name: "index_namespaces_on_created_at", using: :btree
add_index "namespaces", ["deleted_at"], name: "index_namespaces_on_deleted_at", using: :btree
add_index "namespaces", ["name"], name: "index_namespaces_on_name", unique: true, using: :btree
add_index "namespaces", ["name"], name: "index_namespaces_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"}
add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree
......
......@@ -503,6 +503,7 @@ PUT /projects/:id/services/pivotaltracker
Parameters:
- `token` (**required**)
- `restrict_to_branch` (optional) - Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches.
### Delete PivotalTracker service
......@@ -661,4 +662,3 @@ Get JetBrains TeamCity CI service settings for a project.
```
GET /projects/:id/services/teamcity
```
......@@ -26,6 +26,7 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps
end
step 'I see prefilled new Merge Request page' do
expect(page).to have_selector('.merge-request-form')
expect(current_path).to eq new_namespace_project_merge_request_path(@project.namespace, @project)
expect(find("#merge_request_target_project_id").value).to eq @project.id.to_s
expect(find("input#merge_request_source_branch").value).to eq "fix"
......
class Spinach::Features::EventFilters < Spinach::FeatureSteps
include WaitForAjax
include SharedAuthentication
include SharedPaths
include SharedProject
......@@ -72,14 +73,20 @@ class Spinach::Features::EventFilters < Spinach::FeatureSteps
end
When 'I click "push" event filter' do
click_link("push_event_filter")
wait_for_ajax
click_link("Push events")
wait_for_ajax
end
When 'I click "team" event filter' do
click_link("team_event_filter")
wait_for_ajax
click_link("Team")
wait_for_ajax
end
When 'I click "merge" event filter' do
click_link("merged_event_filter")
wait_for_ajax
click_link("Merge events")
wait_for_ajax
end
end
......@@ -43,9 +43,14 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
step 'I click "All" link' do
find(".js-author-search").click
expect(page).to have_selector(".dropdown-menu-author li a")
find(".dropdown-menu-author li a", match: :first).click
expect(page).not_to have_selector(".dropdown-menu-author li a")
find(".js-assignee-search").click
expect(page).to have_selector(".dropdown-menu-assignee li a")
find(".dropdown-menu-assignee li a", match: :first).click
expect(page).not_to have_selector(".dropdown-menu-assignee li a")
end
def should_see(issue)
......
......@@ -47,9 +47,14 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
step 'I click "All" link' do
find(".js-author-search").click
expect(page).to have_selector(".dropdown-menu-author li a")
find(".dropdown-menu-author li a", match: :first).click
expect(page).not_to have_selector(".dropdown-menu-author li a")
find(".js-assignee-search").click
expect(page).to have_selector(".dropdown-menu-assignee li a")
find(".dropdown-menu-assignee li a", match: :first).click
expect(page).not_to have_selector(".dropdown-menu-assignee li a")
end
def should_see(merge_request)
......
......@@ -29,6 +29,7 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps
end
step 'I am redirected to the GitHub import page' do
expect(page).to have_content('Import Projects from GitHub')
expect(current_path).to eq new_import_github_path
end
......@@ -47,6 +48,7 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps
end
step 'I redirected to Google Code import page' do
expect(page).to have_content('Import projects from Google Code')
expect(current_path).to eq new_import_google_code_path
end
end
......@@ -10,6 +10,7 @@ class Spinach::Features::ProjectBuildsArtifacts < Spinach::FeatureSteps
step 'I click artifacts browse button' do
click_link 'Browse'
expect(page).not_to have_selector('.build-sidebar')
end
step 'I should see content of artifacts archive' do
......
......@@ -34,6 +34,9 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
end
step 'I fill out a "Merge Request On Forked Project" merge request' do
expect(page).to have_content('Source branch')
expect(page).to have_content('Target branch')
first('.js-source-project').click
first('.dropdown-source-project a', text: @forked_project.path_with_namespace)
......
......@@ -354,6 +354,8 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end
def filter_issue(text)
sleep 1
fill_in 'issue_search', with: text
sleep 1
end
end
......@@ -489,10 +489,12 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I fill in merge request search with "Fe"' do
sleep 1
fill_in 'issue_search', with: "Fe"
end
step 'I click the "Target branch" dropdown' do
expect(page).to have_content('Target branch')
first('.target_branch').click
end
......
......@@ -69,6 +69,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I edit code' do
expect(page).to have_selector('.file-editor')
set_new_content
end
......@@ -131,6 +132,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
step 'I click on "New file" link in repo' do
find('.add-to-tree').click
click_link 'New file'
expect(page).to have_selector('.file-editor')
end
step 'I click on "Upload file" link in repo' do
......
......@@ -142,7 +142,9 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I edit the Wiki page with a path' do
expect(page).to have_content('three')
click_on 'three'
expect(find('.nav-text')).to have_content('Three')
click_on 'Edit'
end
......
......@@ -133,9 +133,7 @@ module SharedIssuable
end
step 'The list should be sorted by "Oldest updated"' do
page.within('.content div.dropdown.inline.prepend-left-10') do
expect(page.find('button.dropdown-toggle.btn')).to have_content('Oldest updated')
end
expect(find('.issues-filters')).to have_content('Oldest updated')
end
step 'I click link "Next" in the sidebar' do
......
module WaitForAjax
def wait_for_ajax
Timeout.timeout(Capybara.default_max_wait_time) do
loop until finished_all_ajax_requests?
end
end
def finished_all_ajax_requests?
page.evaluate_script('jQuery.active').zero?
end
end
......@@ -61,9 +61,9 @@ module API
#
delete ':id' do
todo = current_user.todos.find(params[:id])
todo.done
TodoService.new.mark_todos_as_done([todo], current_user)
present todo, with: Entities::Todo, current_user: current_user
present todo.reload, with: Entities::Todo, current_user: current_user
end
# Mark all todos as done
......@@ -73,9 +73,7 @@ module API
#
delete do
todos = find_todos
todos.each(&:done)
todos.length
TodoService.new.mark_todos_as_done(todos, current_user)
end
end
end
......
......@@ -7,7 +7,7 @@ module Banzai
UNSAFE_PROTOCOLS = %w(data javascript vbscript).freeze
def whitelist
whitelist = super
whitelist = super.dup
customize_whitelist(whitelist)
......@@ -42,6 +42,8 @@ module Banzai
# Allow any protocol in `a` elements...
whitelist[:protocols].delete('a')
whitelist[:transformers] = whitelist[:transformers].dup
# ...but then remove links with unsafe protocols
whitelist[:transformers].push(remove_unsafe_links)
......
......@@ -11,7 +11,7 @@ module Gitlab
end
def exec
error = protected_branch_checks || tag_checks || push_checks
error = push_checks || tag_checks || protected_branch_checks
if error
GitAccessStatus.new(false, error)
......
......@@ -20,10 +20,11 @@ if [ -f /.dockerenv ] || [ -f ./dockerinit ]; then
# Install phantomjs package
pushd vendor/apt
if [ ! -e phantomjs_1.9.8-0jessie_amd64.deb ]; then
wget -q https://gitlab.com/axil/phantomjs-debian/raw/master/phantomjs_1.9.8-0jessie_amd64.deb
PHANTOMJS_FILE="phantomjs-$PHANTOMJS_VERSION-linux-x86_64"
if [ ! -d "$PHANTOMJS_FILE" ]; then
curl -q -L "https://s3.amazonaws.com/gitlab-build-helpers/$PHANTOMJS_FILE.tar.bz2" | tar jx
fi
dpkg -i phantomjs_1.9.8-0jessie_amd64.deb
cp "$PHANTOMJS_FILE/bin/phantomjs" "/usr/bin/"
popd
# Try to install packages
......
require 'spec_helper'
describe Admin::GroupsController do
let(:group) { create(:group) }
let(:project) { create(:project, namespace: group) }
let(:admin) { create(:admin) }
before do
sign_in(admin)
Sidekiq::Testing.fake!
end
describe 'DELETE #destroy' do
it 'schedules a group destroy' do
expect { delete :destroy, id: project.group.path }.to change(GroupDestroyWorker.jobs, :size).by(1)
end
it 'redirects to the admin group path' do
delete :destroy, id: project.group.path
expect(response).to redirect_to(admin_groups_path)
end
end
end
......@@ -75,4 +75,33 @@ describe GroupsController do
end
end
end
describe 'DELETE #destroy' do
context 'as another user' do
it 'returns 404' do
sign_in(create(:user))
delete :destroy, id: group.path
expect(response.status).to eq(404)
end
end
context 'as the group owner' do
before do
Sidekiq::Testing.fake!
sign_in(user)
end
it 'schedules a group destroy' do
expect { delete :destroy, id: group.path }.to change(GroupDestroyWorker.jobs, :size).by(1)
end
it 'redirects to the root path' do
delete :destroy, id: group.path
expect(response).to redirect_to(root_path)
end
end
end
end
......@@ -55,7 +55,7 @@ describe 'Projects > Issuables > Default sort order', feature: true do
it 'is "last updated"' do
visit_merge_requests_with_state(project, 'merged')
expect(selected_sort_order).to eq('last updated')
expect(find('.issues-other-filters')).to have_content('Last updated')
expect(first_merge_request).to include(last_updated_issuable.title)
expect(last_merge_request).to include(first_updated_issuable.title)
end
......@@ -67,7 +67,7 @@ describe 'Projects > Issuables > Default sort order', feature: true do
it 'is "last updated"' do
visit_merge_requests_with_state(project, 'closed')
expect(selected_sort_order).to eq('last updated')
expect(find('.issues-other-filters')).to have_content('Last updated')
expect(first_merge_request).to include(last_updated_issuable.title)
expect(last_merge_request).to include(first_updated_issuable.title)
end
......@@ -79,7 +79,7 @@ describe 'Projects > Issuables > Default sort order', feature: true do
it 'is "last created"' do
visit_merge_requests_with_state(project, 'all')
expect(selected_sort_order).to eq('last created')
expect(find('.issues-other-filters')).to have_content('Last created')
expect(first_merge_request).to include(last_created_issuable.title)
expect(last_merge_request).to include(first_created_issuable.title)
end
......@@ -108,7 +108,7 @@ describe 'Projects > Issuables > Default sort order', feature: true do
it 'is "last created"' do
visit_issues project
expect(selected_sort_order).to eq('last created')
expect(find('.issues-other-filters')).to have_content('Last created')
expect(first_issue).to include(last_created_issuable.title)
expect(last_issue).to include(first_created_issuable.title)
end
......@@ -120,7 +120,7 @@ describe 'Projects > Issuables > Default sort order', feature: true do
it 'is "last created"' do
visit_issues_with_state(project, 'open')
expect(selected_sort_order).to eq('last created')
expect(find('.issues-other-filters')).to have_content('Last created')
expect(first_issue).to include(last_created_issuable.title)
expect(last_issue).to include(first_created_issuable.title)
end
......@@ -132,7 +132,7 @@ describe 'Projects > Issuables > Default sort order', feature: true do
it 'is "last updated"' do
visit_issues_with_state(project, 'closed')
expect(selected_sort_order).to eq('last updated')
expect(find('.issues-other-filters')).to have_content('Last updated')
expect(first_issue).to include(last_updated_issuable.title)
expect(last_issue).to include(first_updated_issuable.title)
end
......@@ -144,7 +144,7 @@ describe 'Projects > Issuables > Default sort order', feature: true do
it 'is "last created"' do
visit_issues_with_state(project, 'all')
expect(selected_sort_order).to eq('last created')
expect(find('.issues-other-filters')).to have_content('Last created')
expect(first_issue).to include(last_created_issuable.title)
expect(last_issue).to include(first_created_issuable.title)
end
......
......@@ -117,7 +117,7 @@ describe 'Filter issues', feature: true do
find('.dropdown-menu-user-link', text: user.username).click
wait_for_ajax
expect(page).not_to have_selector('.issues-list .issue')
find('.js-label-select').click
......
......@@ -13,6 +13,8 @@ feature 'Create New Merge Request', feature: true, js: true do
it 'generates a diff for an orphaned branch' do
click_link 'New Merge Request'
expect(page).to have_content('Source branch')
expect(page).to have_content('Target branch')
first('.js-source-branch').click
first('.dropdown-source-branch .dropdown-content a', text: 'orphaned-branch').click
......
......@@ -68,10 +68,14 @@ describe 'Profile > Preferences', feature: true do
allowing_for_delay do
find('#logo').click
expect(page).to have_content("You don't have starred projects yet")
expect(page.current_path).to eq starred_dashboard_projects_path
end
click_link 'Your Projects'
expect(page).not_to have_content("You don't have starred projects yet")
expect(page.current_path).to eq dashboard_projects_path
end
end
......
......@@ -39,6 +39,7 @@ feature 'project owner creates a license file', feature: true, js: true do
scenario 'project master creates a license file from the "Add license" link' do
click_link 'Add License'
expect(page).to have_content('New File')
expect(current_path).to eq(
namespace_project_new_blob_path(project.namespace, project, 'master'))
expect(find('#file_name').value).to eq('LICENSE')
......
......@@ -14,6 +14,7 @@ feature 'project owner sees a link to create a license file in empty project', f
visit namespace_project_path(project.namespace, project)
click_link 'Create empty bare repository'
click_on 'LICENSE'
expect(page).to have_content('New File')
expect(current_path).to eq(
namespace_project_new_blob_path(project.namespace, project, 'master'))
......
require 'spec_helper'
feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: true, js: true do
include WaitForAjax
before { allow_any_instance_of(U2fHelper).to receive(:inject_u2f_api?).and_return(true) }
def manage_two_factor_authentication
click_on 'Manage Two-Factor Authentication'
expect(page).to have_content("Setup New U2F Device")
wait_for_ajax
end
def register_u2f_device(u2f_device = nil)
u2f_device ||= FakeU2fDevice.new(page)
u2f_device.respond_to_u2f_registration
......@@ -34,7 +42,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
describe 'when 2FA via OTP is enabled' do
it 'allows registering a new device' do
visit profile_account_path
click_on 'Manage Two-Factor Authentication'
manage_two_factor_authentication
expect(page.body).to match("You've already enabled two-factor authentication using mobile")
register_u2f_device
......@@ -46,15 +54,15 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
visit profile_account_path
# First device
click_on 'Manage Two-Factor Authentication'
manage_two_factor_authentication
register_u2f_device
expect(page.body).to match('Your U2F device was registered')
# Second device
click_on 'Manage Two-Factor Authentication'
manage_two_factor_authentication
register_u2f_device
expect(page.body).to match('Your U2F device was registered')
click_on 'Manage Two-Factor Authentication'
manage_two_factor_authentication
expect(page.body).to match('You have 2 U2F devices registered')
end
end
......@@ -62,7 +70,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
it 'allows the same device to be registered for multiple users' do
# First user
visit profile_account_path
click_on 'Manage Two-Factor Authentication'
manage_two_factor_authentication
u2f_device = register_u2f_device
expect(page.body).to match('Your U2F device was registered')
logout
......@@ -71,7 +79,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
user = login_as(:user)
user.update_attribute(:otp_required_for_login, true)
visit profile_account_path
click_on 'Manage Two-Factor Authentication'
manage_two_factor_authentication
register_u2f_device(u2f_device)
expect(page.body).to match('Your U2F device was registered')
......@@ -81,7 +89,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
context "when there are form errors" do
it "doesn't register the device if there are errors" do
visit profile_account_path
click_on 'Manage Two-Factor Authentication'
manage_two_factor_authentication
# Have the "u2f device" respond with bad data
page.execute_script("u2f.register = function(_,_,_,callback) { callback('bad response'); };")
......@@ -96,7 +104,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
it "allows retrying registration" do
visit profile_account_path
click_on 'Manage Two-Factor Authentication'
manage_two_factor_authentication
# Failed registration
page.execute_script("u2f.register = function(_,_,_,callback) { callback('bad response'); };")
......@@ -122,7 +130,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
login_as(user)
user.update_attribute(:otp_required_for_login, true)
visit profile_account_path
click_on 'Manage Two-Factor Authentication'
manage_two_factor_authentication
@u2f_device = register_u2f_device
logout
end
......@@ -161,7 +169,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
current_user = login_as(:user)
current_user.update_attribute(:otp_required_for_login, true)
visit profile_account_path
click_on 'Manage Two-Factor Authentication'
manage_two_factor_authentication
register_u2f_device
logout
......@@ -182,7 +190,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
current_user = login_as(:user)
current_user.update_attribute(:otp_required_for_login, true)
visit profile_account_path
click_on 'Manage Two-Factor Authentication'
manage_two_factor_authentication
register_u2f_device(@u2f_device)
logout
......@@ -248,7 +256,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
user = login_as(:user)
user.update_attribute(:otp_required_for_login, true)
visit profile_account_path
click_on 'Manage Two-Factor Authentication'
manage_two_factor_authentication
expect(page).to have_content("Your U2F device needs to be set up.")
register_u2f_device
end
......
......@@ -42,6 +42,7 @@ describe 'Project variables', js: true do
find('.btn-variable-edit').click
end
expect(page).to have_content('Update variable')
fill_in('variable_key', with: 'key')
fill_in('variable_value', with: 'key value')
click_button('Save variable')
......
......@@ -23,7 +23,6 @@ describe ProjectsFinder do
let(:finder) { described_class.new }
describe 'without a group' do
describe 'without a user' do
subject { finder.execute }
......@@ -43,53 +42,17 @@ describe ProjectsFinder do
end
it do
is_expected.to eq([public_project, internal_project,
private_project])
is_expected.to eq([public_project, internal_project, private_project])
end
end
end
end
describe 'with a group' do
describe 'without a user' do
subject { finder.execute(nil, group: group) }
it { is_expected.to eq([public_project]) }
end
describe 'with a user' do
subject { finder.execute(user, group: group) }
describe 'with project_ids_relation' do
let(:project_ids_relation) { Project.where(id: internal_project.id) }
describe 'without shared projects' do
it { is_expected.to eq([public_project, internal_project]) }
end
describe 'with shared projects and group membership' do
before do
group.add_user(user, Gitlab::Access::DEVELOPER)
shared_project.project_group_links.
create(group_access: Gitlab::Access::MASTER, group: group)
end
subject { finder.execute(user, project_ids_relation) }
it do
is_expected.to eq([shared_project, public_project, internal_project])
end
end
describe 'with shared projects and project membership' do
before do
shared_project.team.add_user(user, Gitlab::Access::DEVELOPER)
shared_project.project_group_links.
create(group_access: Gitlab::Access::MASTER, group: group)
end
it do
is_expected.to eq([shared_project, public_project, internal_project])
end
end
end
it { is_expected.to eq([internal_project]) }
end
end
end
require 'spec_helper'
describe Gitlab::Checks::ChangeAccess, lib: true do
describe '#exec' do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:user_access) { Gitlab::UserAccess.new(user, project: project) }
let(:changes) do
{
oldrev: 'be93687618e4b132087f430a4d8fc3a609c9b77c',
newrev: '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51',
ref: 'refs/heads/master'
}
end
subject { described_class.new(changes, project: project, user_access: user_access).exec }
before { allow(user_access).to receive(:can_do_action?).with(:push_code).and_return(true) }
context 'without failed checks' do
it "doesn't return any error" do
expect(subject.status).to be(true)
end
end
context 'when the user is not allowed to push code' do
it 'returns an error' do
expect(user_access).to receive(:can_do_action?).with(:push_code).and_return(false)
expect(subject.status).to be(false)
expect(subject.message).to eq('You are not allowed to push code to this project.')
end
end
context 'tags check' do
let(:changes) do
{
oldrev: 'be93687618e4b132087f430a4d8fc3a609c9b77c',
newrev: '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51',
ref: 'refs/tags/v1.0.0'
}
end
it 'returns an error if the user is not allowed to update tags' do
expect(user_access).to receive(:can_do_action?).with(:admin_project).and_return(false)
expect(subject.status).to be(false)
expect(subject.message).to eq('You are not allowed to change existing tags on this project.')
end
end
context 'protected branches check' do
before do
allow(project).to receive(:protected_branch?).with('master').and_return(true)
end
it 'returns an error if the user is not allowed to do forced pushes to protected branches' do
expect(Gitlab::Checks::ForcePush).to receive(:force_push?).and_return(true)
expect(user_access).to receive(:can_do_action?).with(:force_push_code_to_protected_branches).and_return(false)
expect(subject.status).to be(false)
expect(subject.message).to eq('You are not allowed to force push code to a protected branch on this project.')
end
it 'returns an error if the user is not allowed to merge to protected branches' do
expect_any_instance_of(Gitlab::Checks::MatchingMergeRequest).to receive(:match?).and_return(true)
expect(user_access).to receive(:can_merge_to_branch?).and_return(false)
expect(user_access).to receive(:can_push_to_branch?).and_return(false)
expect(subject.status).to be(false)
expect(subject.message).to eq('You are not allowed to merge code into protected branches on this project.')
end
it 'returns an error if the user is not allowed to push to protected branches' do
expect(user_access).to receive(:can_push_to_branch?).and_return(false)
expect(subject.status).to be(false)
expect(subject.message).to eq('You are not allowed to push code to protected branches on this project.')
end
context 'branch deletion' do
let(:changes) do
{
oldrev: 'be93687618e4b132087f430a4d8fc3a609c9b77c',
newrev: '0000000000000000000000000000000000000000',
ref: 'refs/heads/master'
}
end
it 'returns an error if the user is not allowed to delete protected branches' do
expect(user_access).to receive(:can_do_action?).with(:remove_protected_branches).and_return(false)
expect(subject.status).to be(false)
expect(subject.message).to eq('You are not allowed to delete protected branches from this project.')
end
end
end
end
end
......@@ -94,4 +94,26 @@ describe Blob do
expect(blob.to_partial_path).to eq 'download'
end
end
describe '#size_within_svg_limits?' do
let(:blob) { described_class.decorate(double(:blob)) }
it 'returns true when the blob size is smaller than the SVG limit' do
expect(blob).to receive(:size).and_return(42)
expect(blob.size_within_svg_limits?).to eq(true)
end
it 'returns true when the blob size is equal to the SVG limit' do
expect(blob).to receive(:size).and_return(Blob::MAXIMUM_SVG_SIZE)
expect(blob.size_within_svg_limits?).to eq(true)
end
it 'returns false when the blob size is larger than the SVG limit' do
expect(blob).to receive(:size).and_return(1.terabyte)
expect(blob.size_within_svg_limits?).to eq(false)
end
end
end
......@@ -15,4 +15,28 @@ describe Deployment, models: true do
it { is_expected.to validate_presence_of(:ref) }
it { is_expected.to validate_presence_of(:sha) }
describe '#includes_commit?' do
let(:project) { create(:project) }
let(:environment) { create(:environment, project: project) }
let(:deployment) do
create(:deployment, environment: environment, sha: project.commit.id)
end
context 'when there is no project commit' do
it 'returns false' do
commit = project.commit('feature')
expect(deployment.includes_commit?(commit)).to be false
end
end
context 'when they share the same tree branch' do
it 'returns true' do
commit = project.commit
expect(deployment.includes_commit?(commit)).to be true
end
end
end
end
......@@ -30,4 +30,37 @@ describe Environment, models: true do
expect(env.external_url).to be_nil
end
end
describe '#includes_commit?' do
context 'without a last deployment' do
it "returns false" do
expect(environment.includes_commit?('HEAD')).to be false
end
end
context 'with a last deployment' do
let(:project) { create(:project) }
let(:environment) { create(:environment, project: project) }
let!(:deployment) do
create(:deployment, environment: environment, sha: project.commit('master').id)
end
context 'in the same branch' do
it 'returns true' do
expect(environment.includes_commit?(RepoHelpers.sample_commit)).to be true
end
end
context 'not in the same branch' do
before do
deployment.update(sha: project.commit('feature').id)
end
it 'returns false' do
expect(environment.includes_commit?(RepoHelpers.sample_commit)).to be false
end
end
end
end
end
......@@ -674,6 +674,21 @@ describe MergeRequest, models: true do
end
end
describe "#environments" do
let(:project) { create(:project) }
let!(:environment) { create(:environment, project: project) }
let!(:environment1) { create(:environment, project: project) }
let!(:environment2) { create(:environment, project: project) }
let(:merge_request) { create(:merge_request, source_project: project) }
it 'selects deployed environments' do
create(:deployment, environment: environment, sha: project.commit('master').id)
create(:deployment, environment: environment1, sha: project.commit('feature').id)
expect(merge_request.environments).to eq [environment]
end
end
describe "#reload_diff" do
let(:note) { create(:diff_note_on_merge_request, project: subject.project, noteable: subject) }
......
......@@ -39,4 +39,75 @@ describe PivotaltrackerService, models: true do
it { is_expected.not_to validate_presence_of(:token) }
end
end
describe 'Execute' do
let(:service) do
PivotaltrackerService.new.tap do |service|
service.token = 'secret_api_token'
end
end
let(:url) { PivotaltrackerService::API_ENDPOINT }
def push_data(branch: 'master')
{
object_kind: 'push',
ref: "refs/heads/#{branch}",
commits: [
{
id: '21c12ea',
author: {
name: 'Some User'
},
url: 'https://example.com/commit',
message: 'commit message',
}
]
}
end
before do
WebMock.stub_request(:post, url)
end
it 'should post correct message' do
service.execute(push_data)
expect(WebMock).to have_requested(:post, url).with(
body: {
'source_commit' => {
'commit_id' => '21c12ea',
'author' => 'Some User',
'url' => 'https://example.com/commit',
'message' => 'commit message'
}
},
headers: {
'Content-Type' => 'application/json',
'X-TrackerToken' => 'secret_api_token'
}
).once
end
context 'when allowed branches is specified' do
let(:service) do
super().tap do |service|
service.restrict_to_branch = 'master,v10'
end
end
it 'should post message if branch is in the list' do
service.execute(push_data(branch: 'master'))
service.execute(push_data(branch: 'v10'))
expect(WebMock).to have_requested(:post, url).twice
end
it 'should not post message if branch is not in the list' do
service.execute(push_data(branch: 'mas'))
service.execute(push_data(branch: 'v11'))
expect(WebMock).not_to have_requested(:post, url)
end
end
end
end
......@@ -117,6 +117,12 @@ describe API::Todos, api: true do
expect(response.status).to eq(200)
expect(pending_1.reload).to be_done
end
it 'updates todos cache' do
expect_any_instance_of(User).to receive(:update_todos_count_cache).and_call_original
delete api("/todos/#{pending_1.id}", john_doe)
end
end
end
......@@ -139,6 +145,12 @@ describe API::Todos, api: true do
expect(pending_2.reload).to be_done
expect(pending_3.reload).to be_done
end
it 'updates todos cache' do
expect_any_instance_of(User).to receive(:update_todos_count_cache).and_call_original
delete api("/todos", john_doe)
end
end
end
......
......@@ -564,12 +564,14 @@ describe API::API, api: true do
end
describe "DELETE /users/:id" do
let!(:namespace) { user.namespace }
before { admin }
it "deletes user" do
delete api("/users/#{user.id}", admin)
expect(response).to have_http_status(200)
expect { User.find(user.id) }.to raise_error ActiveRecord::RecordNotFound
expect { Namespace.find(namespace.id) }.to raise_error ActiveRecord::RecordNotFound
expect(json_response['email']).to eq(user.email)
end
......
......@@ -9,9 +9,11 @@ describe DeleteUserService, services: true do
context 'no options are given' do
it 'deletes the user' do
DeleteUserService.new(current_user).execute(user)
user_data = DeleteUserService.new(current_user).execute(user)
expect { user_data['email'].to eq(user.email) }
expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
expect { Namespace.with_deleted.find(user.namespace.id) }.to raise_error(ActiveRecord::RecordNotFound)
end
it 'will delete the project in the near future' do
......
......@@ -7,9 +7,10 @@ describe DestroyGroupService, services: true do
let!(:gitlab_shell) { Gitlab::Shell.new }
let!(:remove_path) { group.path + "+#{group.id}+deleted" }
shared_examples 'group destruction' do |async|
context 'database records' do
before do
destroy_group(group, user)
destroy_group(group, user, async)
end
it { expect(Group.all).not_to include(group) }
......@@ -20,7 +21,7 @@ describe DestroyGroupService, services: true do
context 'Sidekiq inline' do
before do
# Run sidekiq immediatly to check that renamed dir will be removed
Sidekiq::Testing.inline! { destroy_group(group, user) }
Sidekiq::Testing.inline! { destroy_group(group, user, async) }
end
it { expect(gitlab_shell.exists?(project.repository_storage_path, group.path)).to be_falsey }
......@@ -30,7 +31,7 @@ describe DestroyGroupService, services: true do
context 'Sidekiq fake' do
before do
# Dont run sidekiq to check if renamed repository exists
Sidekiq::Testing.fake! { destroy_group(group, user) }
Sidekiq::Testing.fake! { destroy_group(group, user, async) }
end
it { expect(gitlab_shell.exists?(project.repository_storage_path, group.path)).to be_falsey }
......@@ -38,7 +39,20 @@ describe DestroyGroupService, services: true do
end
end
def destroy_group(group, user)
def destroy_group(group, user, async)
if async
DestroyGroupService.new(group, user).async_execute
else
DestroyGroupService.new(group, user).execute
end
end
end
describe 'asynchronous delete' do
it_behaves_like 'group destruction', true
end
describe 'synchronous delete' do
it_behaves_like 'group destruction', false
end
end
......@@ -7,7 +7,9 @@ describe MergeRequests::GetUrlsService do
let(:new_merge_request_url) { "http://localhost/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=#{source_branch}" }
let(:show_merge_request_url) { "http://localhost/#{project.namespace.name}/#{project.path}/merge_requests/#{merge_request.iid}" }
let(:new_branch_changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" }
let(:deleted_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 #{Gitlab::Git::BLANK_SHA} refs/heads/#{source_branch}" }
let(:existing_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" }
let(:default_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/master" }
describe "#execute" do
shared_examples 'new_merge_request_link' do
......@@ -32,6 +34,28 @@ describe MergeRequests::GetUrlsService do
end
end
shared_examples 'no_merge_request_url' do
it 'returns no URL' do
result = service.execute(changes)
expect(result).to be_empty
end
end
context 'pushing to default branch' do
let(:changes) { default_branch_changes }
it_behaves_like 'no_merge_request_url'
end
context 'pushing to project with MRs disabled' do
let(:changes) { new_branch_changes }
before do
project.merge_requests_enabled = false
end
it_behaves_like 'no_merge_request_url'
end
context 'pushing one completely new branch' do
let(:changes) { new_branch_changes }
it_behaves_like 'new_merge_request_link'
......@@ -42,6 +66,11 @@ describe MergeRequests::GetUrlsService do
it_behaves_like 'new_merge_request_link'
end
context 'pushing to deleted branch' do
let(:changes) { deleted_branch_changes }
it_behaves_like 'no_merge_request_url'
end
context 'pushing to existing branch and merge request opened' do
let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch) }
let(:changes) { existing_branch_changes }
......@@ -61,6 +90,11 @@ describe MergeRequests::GetUrlsService do
let(:changes) { existing_branch_changes }
# Source project is now the forked one
let(:service) { MergeRequests::GetUrlsService.new(forked_project) }
before do
allow(forked_project).to receive(:empty_repo?).and_return(false)
end
it_behaves_like 'show_merge_request_url'
end
......
......@@ -472,6 +472,42 @@ describe TodoService, services: true do
expect(john_doe.todos_pending_count).to eq(1)
end
describe '#mark_todos_as_done' do
let(:issue) { create(:issue, project: project, author: author, assignee: john_doe) }
it 'marks a relation of todos as done' do
create(:todo, :mentioned, user: john_doe, target: issue, project: project)
todos = TodosFinder.new(john_doe, {}).execute
expect { TodoService.new.mark_todos_as_done(todos, john_doe) }
.to change { john_doe.todos.done.count }.from(0).to(1)
end
it 'marks an array of todos as done' do
todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project)
expect { TodoService.new.mark_todos_as_done([todo], john_doe) }
.to change { todo.reload.state }.from('pending').to('done')
end
it 'returns the number of updated todos' do # Needed on API
todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project)
expect(TodoService.new.mark_todos_as_done([todo], john_doe)).to eq(1)
end
it 'caches the number of todos of a user', :caching do
create(:todo, :mentioned, user: john_doe, target: issue, project: project)
todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project)
TodoService.new.mark_todos_as_done([todo], john_doe)
expect_any_instance_of(TodosFinder).not_to receive(:execute)
expect(john_doe.todos_done_count).to eq(1)
expect(john_doe.todos_pending_count).to eq(1)
end
end
def should_create_todo(attributes = {})
attributes.reverse_merge!(
project: project,
......
require 'spec_helper'
describe 'projects/merge_requests/widget/_heading' do
include Devise::TestHelpers
context 'when released to an environment' do
let(:project) { merge_request.target_project }
let(:merge_request) { create(:merge_request, :merged) }
let(:environment) { create(:environment, project: project) }
let!(:deployment) do
create(:deployment, environment: environment, sha: project.commit('master').id)
end
before do
assign(:merge_request, merge_request)
assign(:project, project)
render
end
it 'displays that the environment is deployed' do
expect(rendered).to match("Deployed to")
expect(rendered).to match("#{environment.name}")
end
end
end
require 'spec_helper'
describe GroupDestroyWorker do
let(:group) { create(:group) }
let(:user) { create(:admin) }
let!(:project) { create(:project, namespace: group) }
subject { GroupDestroyWorker.new }
describe "#perform" do
it "deletes the project" do
subject.perform(group.id, user.id)
expect(Group.all).not_to include(group)
expect(Project.all).not_to include(project)
expect(Dir.exist?(project.path)).to be_falsey
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment