summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml2
-rw-r--r--CHANGELOG.md3
-rw-r--r--app/assets/javascripts/boards/stores/boards_store.js.es63
-rw-r--r--app/controllers/projects_controller.rb2
-rw-r--r--app/finders/labels_finder.rb15
-rw-r--r--app/models/ci/pipeline.rb12
-rw-r--r--app/models/commit_status.rb12
-rw-r--r--app/models/project.rb2
-rw-r--r--app/models/project_team.rb10
-rw-r--r--app/services/ci/process_pipeline_service.rb23
-rw-r--r--app/services/ci/register_build_service.rb11
-rw-r--r--app/services/labels/find_or_create_service.rb11
-rw-r--r--app/services/members/approve_access_request_service.rb21
-rw-r--r--db/migrate/20161021114307_add_lock_version_to_build_and_pipelines.rb14
-rw-r--r--db/schema.rb2
-rw-r--r--doc/development/frontend.md14
-rw-r--r--lib/banzai/filter/label_reference_filter.rb2
-rw-r--r--lib/gitlab/fogbugz_import/importer.rb4
-rw-r--r--lib/gitlab/github_import/label_formatter.rb4
-rw-r--r--lib/gitlab/google_code_import/importer.rb4
-rw-r--r--lib/gitlab/issues_labels.rb2
-rw-r--r--lib/gitlab/optimistic_locking.rb19
-rw-r--r--lib/tasks/teaspoon.rake23
-rw-r--r--spec/controllers/projects/labels_controller_spec.rb21
-rw-r--r--spec/javascripts/boards/boards_store_spec.js.es65
-rw-r--r--spec/javascripts/fixtures/.gitignore1
-rw-r--r--spec/javascripts/fixtures/issues.rb44
-rw-r--r--spec/javascripts/fixtures/issues_show.html.haml23
-rw-r--r--spec/javascripts/issue_spec.js184
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml2
-rw-r--r--spec/lib/gitlab/optimistic_locking_spec.rb39
-rw-r--r--spec/models/ci/pipeline_spec.rb28
-rw-r--r--spec/policies/issues_policy_spec.rb193
-rw-r--r--spec/requests/api/builds_spec.rb2
-rw-r--r--spec/services/ci/register_build_service_spec.rb4
-rw-r--r--spec/services/labels/find_or_create_service_spec.rb51
-rw-r--r--spec/services/members/approve_access_request_service_spec.rb63
-rw-r--r--spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb2
-rw-r--r--spec/spec_helper.rb2
-rw-r--r--spec/support/javascript_fixtures_helpers.rb45
40 files changed, 713 insertions, 211 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 3f315550536..d04069df885 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -249,7 +249,7 @@ teaspoon:
- curl --silent --location https://deb.nodesource.com/setup_6.x | bash -
- apt-get install --assume-yes nodejs
- npm install --global istanbul
- - teaspoon
+ - rake teaspoon
artifacts:
name: coverage-javascript
expire_in: 31d
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 38e81758c0b..525ea451927 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@ Please view this file on the master branch, on stable branches it's out of date.
- API: Fix booleans not recognized as such when using the `to_boolean` helper
- Removed delete branch tooltip !6954
- Stop unauthorized users dragging on milestone page (blackst0ne)
+ - Restore issue boards welcome message when a project is created !6899
- Escape ref and path for relative links !6050 (winniehell)
- Fixed link typo on /help/ui to Alerts section. !6915 (Sam Rose)
- Fix filtering of milestones with quotes in title (airatshigapov)
@@ -90,6 +91,7 @@ Please view this file on the master branch, on stable branches it's out of date.
- Updating verbiage on git basics to be more intuitive
- Fix project_feature record not generated on project creation
- Clarify documentation for Runners API (Gennady Trafimenkov)
+ - Use optimistic locking for pipelines and builds
- The instrumentation for Banzai::Renderer has been restored
- Change user & group landing page routing from /u/:username to /:username
- Added documentation for .gitattributes files
@@ -157,6 +159,7 @@ Please view this file on the master branch, on stable branches it's out of date.
- Only update issuable labels if they have been changed
- Take filters in account in issuable counters. !6496
- Use custom Ruby images to test builds (registry.dev.gitlab.org/gitlab/gitlab-build-images:*)
+ - Replace static issue fixtures by script !6059 (winniehell)
- Append issue template to existing description !6149 (Joseph Frazier)
- Trending projects now only show public projects and the list of projects is cached for a day
- Memoize Gitlab Shell's secret token (!6599, Justin DiPierro)
diff --git a/app/assets/javascripts/boards/stores/boards_store.js.es6 b/app/assets/javascripts/boards/stores/boards_store.js.es6
index 534845cd8a2..175e034afed 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js.es6
+++ b/app/assets/javascripts/boards/stores/boards_store.js.es6
@@ -63,7 +63,8 @@
this.removeList('blank');
Cookies.set('issue_board_welcome_hidden', 'true', {
- expires: 365 * 10
+ expires: 365 * 10,
+ path: ''
});
},
welcomeIsHidden () {
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 76b730198d4..8c148ecfaeb 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -30,6 +30,8 @@ class ProjectsController < Projects::ApplicationController
@project = ::Projects::CreateService.new(current_user, project_params).execute
if @project.saved?
+ cookies[:issue_board_welcome_hidden] = { path: project_path(@project), value: nil, expires: Time.at(0) }
+
redirect_to(
project_path(@project),
notice: "Project '#{@project.name}' was successfully created."
diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb
index 44484d64567..865f093f04a 100644
--- a/app/finders/labels_finder.rb
+++ b/app/finders/labels_finder.rb
@@ -4,9 +4,8 @@ class LabelsFinder < UnionFinder
@params = params
end
- def execute(authorized_only: true)
- @authorized_only = authorized_only
-
+ def execute(skip_authorization: false)
+ @skip_authorization = skip_authorization
items = find_union(label_ids, Label)
items = with_title(items)
sort(items)
@@ -14,7 +13,7 @@ class LabelsFinder < UnionFinder
private
- attr_reader :current_user, :params, :authorized_only
+ attr_reader :current_user, :params, :skip_authorization
def label_ids
label_ids = []
@@ -70,17 +69,17 @@ class LabelsFinder < UnionFinder
end
def find_project
- if authorized_only
- available_projects.find_by(id: project_id)
- else
+ if skip_authorization
Project.find_by(id: project_id)
+ else
+ available_projects.find_by(id: project_id)
end
end
def projects
return @projects if defined?(@projects)
- @projects = authorized_only ? available_projects : Project.all
+ @projects = skip_authorization ? Project.all : available_projects
@projects = @projects.in_namespace(group_id) if group_id
@projects = @projects.where(id: projects_ids) if projects_ids
@projects = @projects.reorder(nil)
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index adda3b8f40c..d3432632899 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -30,23 +30,23 @@ module Ci
end
event :run do
- transition any => :running
+ transition any - [:running] => :running
end
event :skip do
- transition any => :skipped
+ transition any - [:skipped] => :skipped
end
event :drop do
- transition any => :failed
+ transition any - [:failed] => :failed
end
event :succeed do
- transition any => :success
+ transition any - [:success] => :success
end
event :cancel do
- transition any => :canceled
+ transition any - [:canceled] => :canceled
end
# IMPORTANT
@@ -260,7 +260,7 @@ module Ci
end
def update_status
- with_lock do
+ Gitlab::OptimisticLocking.retry_lock(self) do
case latest_builds_status
when 'pending' then enqueue
when 'running' then run
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 4cb3a69416e..d159fc6c5c7 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -73,16 +73,16 @@ class CommitStatus < ActiveRecord::Base
transition [:created, :pending, :running] => :canceled
end
- after_transition created: [:pending, :running] do |commit_status|
- commit_status.update_attributes queued_at: Time.now
+ before_transition created: [:pending, :running] do |commit_status|
+ commit_status.queued_at = Time.now
end
- after_transition [:created, :pending] => :running do |commit_status|
- commit_status.update_attributes started_at: Time.now
+ before_transition [:created, :pending] => :running do |commit_status|
+ commit_status.started_at = Time.now
end
- after_transition any => [:success, :failed, :canceled] do |commit_status|
- commit_status.update_attributes finished_at: Time.now
+ before_transition any => [:success, :failed, :canceled] do |commit_status|
+ commit_status.finished_at = Time.now
end
after_transition do |commit_status, transition|
diff --git a/app/models/project.rb b/app/models/project.rb
index fbf7012972e..ae57815c620 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -738,7 +738,7 @@ class Project < ActiveRecord::Base
def create_labels
Label.templates.each do |label|
params = label.attributes.except('id', 'template', 'created_at', 'updated_at')
- Labels::FindOrCreateService.new(owner, self, params).execute
+ Labels::FindOrCreateService.new(nil, self, params).execute(skip_authorization: true)
end
end
diff --git a/app/models/project_team.rb b/app/models/project_team.rb
index 79d041d2775..a6e911df9bd 100644
--- a/app/models/project_team.rb
+++ b/app/models/project_team.rb
@@ -125,14 +125,8 @@ class ProjectTeam
max_member_access(user.id) == Gitlab::Access::MASTER
end
- def member?(user, min_member_access = nil)
- member = !!find_member(user.id)
-
- if min_member_access
- member && max_member_access(user.id) >= min_member_access
- else
- member
- end
+ def member?(user, min_member_access = Gitlab::Access::GUEST)
+ max_member_access(user.id) >= min_member_access
end
def human_max_access(user_id)
diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb
index d3dd30b2588..8face432d97 100644
--- a/app/services/ci/process_pipeline_service.rb
+++ b/app/services/ci/process_pipeline_service.rb
@@ -10,17 +10,14 @@ module Ci
create_builds!
end
- @pipeline.with_lock do
- new_builds =
- stage_indexes_of_created_builds.map do |index|
- process_stage(index)
- end
+ new_builds =
+ stage_indexes_of_created_builds.map do |index|
+ process_stage(index)
+ end
- @pipeline.update_status
+ @pipeline.update_status
- # Return a flag if a when builds got enqueued
- new_builds.flatten.any?
- end
+ new_builds.flatten.any?
end
private
@@ -32,9 +29,11 @@ module Ci
def process_stage(index)
current_status = status_for_prior_stages(index)
- created_builds_in_stage(index).select do |build|
- if HasStatus::COMPLETED_STATUSES.include?(current_status)
- process_build(build, current_status)
+ if HasStatus::COMPLETED_STATUSES.include?(current_status)
+ created_builds_in_stage(index).select do |build|
+ Gitlab::OptimisticLocking.retry_lock(build) do |subject|
+ process_build(subject, current_status)
+ end
end
end
end
diff --git a/app/services/ci/register_build_service.rb b/app/services/ci/register_build_service.rb
index 6973191b203..74b5ebf372b 100644
--- a/app/services/ci/register_build_service.rb
+++ b/app/services/ci/register_build_service.rb
@@ -28,17 +28,14 @@ module Ci
if build
# In case when 2 runners try to assign the same build, second runner will be declined
- # with StateMachines::InvalidTransition in run! method.
- build.with_lock do
- build.runner_id = current_runner.id
- build.save!
- build.run!
- end
+ # with StateMachines::InvalidTransition or StaleObjectError when doing run! or save method.
+ build.runner_id = current_runner.id
+ build.run!
end
build
- rescue StateMachines::InvalidTransition
+ rescue StateMachines::InvalidTransition, ActiveRecord::StaleObjectError
nil
end
diff --git a/app/services/labels/find_or_create_service.rb b/app/services/labels/find_or_create_service.rb
index 74291312c4e..d622f9edd33 100644
--- a/app/services/labels/find_or_create_service.rb
+++ b/app/services/labels/find_or_create_service.rb
@@ -2,21 +2,24 @@ module Labels
class FindOrCreateService
def initialize(current_user, project, params = {})
@current_user = current_user
- @group = project.group
@project = project
@params = params.dup
end
- def execute
+ def execute(skip_authorization: false)
+ @skip_authorization = skip_authorization
find_or_create_label
end
private
- attr_reader :current_user, :group, :project, :params
+ attr_reader :current_user, :project, :params, :skip_authorization
def available_labels
- @available_labels ||= LabelsFinder.new(current_user, project_id: project.id).execute
+ @available_labels ||= LabelsFinder.new(
+ current_user,
+ project_id: project.id
+ ).execute(skip_authorization: skip_authorization)
end
def find_or_create_label
diff --git a/app/services/members/approve_access_request_service.rb b/app/services/members/approve_access_request_service.rb
index 416aee2ab51..c13f289f61e 100644
--- a/app/services/members/approve_access_request_service.rb
+++ b/app/services/members/approve_access_request_service.rb
@@ -4,17 +4,25 @@ module Members
attr_accessor :source
+ # source - The source object that respond to `#requesters` (i.g. project or group)
+ # current_user - The user that performs the access request approval
+ # params - A hash of parameters
+ # :user_id - User ID used to retrieve the access requester
+ # :id - Member ID used to retrieve the access requester
+ # :access_level - Optional access level set when the request is accepted
def initialize(source, current_user, params = {})
@source = source
@current_user = current_user
- @params = params
+ @params = params.slice(:user_id, :id, :access_level)
end
- def execute
+ # opts - A hash of options
+ # :force - Bypass permission check: current_user can be nil in that case
+ def execute(opts = {})
condition = params[:user_id] ? { user_id: params[:user_id] } : { id: params[:id] }
access_requester = source.requesters.find_by!(condition)
- raise Gitlab::Access::AccessDeniedError unless can_update_access_requester?(access_requester)
+ raise Gitlab::Access::AccessDeniedError unless can_update_access_requester?(access_requester, opts)
access_requester.access_level = params[:access_level] if params[:access_level]
access_requester.accept_request
@@ -24,8 +32,11 @@ module Members
private
- def can_update_access_requester?(access_requester)
- access_requester && can?(current_user, action_member_permission(:update, access_requester), access_requester)
+ def can_update_access_requester?(access_requester, opts = {})
+ access_requester && (
+ opts[:force] ||
+ can?(current_user, action_member_permission(:update, access_requester), access_requester)
+ )
end
end
end
diff --git a/db/migrate/20161021114307_add_lock_version_to_build_and_pipelines.rb b/db/migrate/20161021114307_add_lock_version_to_build_and_pipelines.rb
new file mode 100644
index 00000000000..b47f3aa2810
--- /dev/null
+++ b/db/migrate/20161021114307_add_lock_version_to_build_and_pipelines.rb
@@ -0,0 +1,14 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddLockVersionToBuildAndPipelines < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def change
+ add_column :ci_builds, :lock_version, :integer
+ add_column :ci_commits, :lock_version, :integer
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 5bbfba0d7dd..54b5fc83be0 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -189,6 +189,7 @@ ActiveRecord::Schema.define(version: 20161025231710) do
t.text "yaml_variables"
t.datetime "queued_at"
t.string "token"
+ t.integer "lock_version"
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
@@ -219,6 +220,7 @@ ActiveRecord::Schema.define(version: 20161025231710) do
t.datetime "finished_at"
t.integer "duration"
t.integer "user_id"
+ t.integer "lock_version"
end
add_index "ci_commits", ["gl_project_id", "sha"], name: "index_ci_commits_on_gl_project_id_and_sha", using: :btree
diff --git a/doc/development/frontend.md b/doc/development/frontend.md
index 56c8516508e..4fb56444917 100644
--- a/doc/development/frontend.md
+++ b/doc/development/frontend.md
@@ -185,6 +185,20 @@ again in the future.
See [the Testing Standards and Style Guidelines](testing.md) for more
information.
+### Running frontend tests
+
+`rake teaspoon` runs the frontend-only (JavaScript) tests.
+It consists of two subtasks:
+
+- `rake teaspoon:fixtures` (re-)generates fixtures
+- `rake teaspoon:tests` actually executes the tests
+
+As long as the fixtures don't change, `rake teaspoon:tests` is sufficient
+(and saves you some time).
+
+Please note: Not all of the frontend fixtures are generated. Some are still static
+files. These will not be touched by `rake teaspoon:fixtures`.
+
## Supported browsers
For our currently-supported browsers, see our [requirements][requirements].
diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb
index c24831e68ee..9f9a96cdc65 100644
--- a/lib/banzai/filter/label_reference_filter.rb
+++ b/lib/banzai/filter/label_reference_filter.rb
@@ -39,7 +39,7 @@ module Banzai
end
def find_labels(project)
- LabelsFinder.new(nil, project_id: project.id).execute(authorized_only: false)
+ LabelsFinder.new(nil, project_id: project.id).execute(skip_authorization: true)
end
# Parameters to pass to `Label.find_by` based on the given arguments
diff --git a/lib/gitlab/fogbugz_import/importer.rb b/lib/gitlab/fogbugz_import/importer.rb
index 65ee85ca5a9..222bcdcbf9c 100644
--- a/lib/gitlab/fogbugz_import/importer.rb
+++ b/lib/gitlab/fogbugz_import/importer.rb
@@ -75,7 +75,7 @@ module Gitlab
def create_label(name)
params = { title: name, color: nice_label_color(name) }
- ::Labels::FindOrCreateService.new(project.owner, project, params).execute
+ ::Labels::FindOrCreateService.new(nil, project, params).execute(skip_authorization: true)
end
def user_info(person_id)
@@ -133,7 +133,7 @@ module Gitlab
updated_at: DateTime.parse(bug['dtLastUpdated'])
)
- issue_labels = ::LabelsFinder.new(project.owner, project_id: project.id, title: labels).execute
+ issue_labels = ::LabelsFinder.new(nil, project_id: project.id, title: labels).execute(skip_authorization: true)
issue.update_attribute(:label_ids, issue_labels.pluck(:id))
import_issue_comments(issue, comments)
diff --git a/lib/gitlab/github_import/label_formatter.rb b/lib/gitlab/github_import/label_formatter.rb
index 002494739e9..211ccdc51bb 100644
--- a/lib/gitlab/github_import/label_formatter.rb
+++ b/lib/gitlab/github_import/label_formatter.rb
@@ -15,8 +15,8 @@ module Gitlab
def create!
params = attributes.except(:project)
- service = ::Labels::FindOrCreateService.new(project.owner, project, params)
- label = service.execute
+ service = ::Labels::FindOrCreateService.new(nil, project, params)
+ label = service.execute(skip_authorization: true)
raise ActiveRecord::RecordInvalid.new(label) unless label.persisted?
diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb
index 6a68e786b4f..1f4edc36928 100644
--- a/lib/gitlab/google_code_import/importer.rb
+++ b/lib/gitlab/google_code_import/importer.rb
@@ -101,7 +101,7 @@ module Gitlab
state: raw_issue['state'] == 'closed' ? 'closed' : 'opened'
)
- issue_labels = ::LabelsFinder.new(project.owner, project_id: project.id, title: labels).execute
+ issue_labels = ::LabelsFinder.new(nil, project_id: project.id, title: labels).execute(skip_authorization: true)
issue.update_attribute(:label_ids, issue_labels.pluck(:id))
import_issue_comments(issue, comments)
@@ -235,7 +235,7 @@ module Gitlab
def create_label(name)
params = { name: name, color: nice_label_color(name) }
- ::Labels::FindOrCreateService.new(project.owner, project, params).execute
+ ::Labels::FindOrCreateService.new(nil, project, params).execute(skip_authorization: true)
end
def format_content(raw_content)
diff --git a/lib/gitlab/issues_labels.rb b/lib/gitlab/issues_labels.rb
index dbc759367eb..b8ca7f2f55f 100644
--- a/lib/gitlab/issues_labels.rb
+++ b/lib/gitlab/issues_labels.rb
@@ -19,7 +19,7 @@ module Gitlab
]
labels.each do |params|
- ::Labels::FindOrCreateService.new(project.owner, project, params).execute
+ ::Labels::FindOrCreateService.new(nil, project, params).execute(skip_authorization: true)
end
end
end
diff --git a/lib/gitlab/optimistic_locking.rb b/lib/gitlab/optimistic_locking.rb
new file mode 100644
index 00000000000..879d46446b3
--- /dev/null
+++ b/lib/gitlab/optimistic_locking.rb
@@ -0,0 +1,19 @@
+module Gitlab
+ module OptimisticLocking
+ extend self
+
+ def retry_lock(subject, retries = 100, &block)
+ loop do
+ begin
+ ActiveRecord::Base.transaction do
+ return block.call(subject)
+ end
+ rescue ActiveRecord::StaleObjectError
+ retries -= 1
+ raise unless retries >= 0
+ subject.reload
+ end
+ end
+ end
+ end
+end
diff --git a/lib/tasks/teaspoon.rake b/lib/tasks/teaspoon.rake
new file mode 100644
index 00000000000..156fa90537d
--- /dev/null
+++ b/lib/tasks/teaspoon.rake
@@ -0,0 +1,23 @@
+Rake::Task['teaspoon'].clear if Rake::Task.task_defined?('teaspoon')
+
+namespace :teaspoon do
+ desc 'GitLab | Teaspoon | Generate fixtures for JavaScript tests'
+ RSpec::Core::RakeTask.new(:fixtures) do |t|
+ ENV['NO_KNAPSACK'] = 'true'
+ t.pattern = 'spec/javascripts/fixtures/*.rb'
+ t.rspec_opts = '--format documentation'
+ end
+
+ desc 'GitLab | Teaspoon | Run JavaScript tests'
+ task :tests do
+ require "teaspoon/console"
+ options = {}
+ abort('rake teaspoon:tests failed') if Teaspoon::Console.new(options).failures?
+ end
+end
+
+desc 'GitLab | Teaspoon | Shortcut for teaspoon:fixtures and teaspoon:tests'
+task :teaspoon do
+ Rake::Task['teaspoon:fixtures'].invoke
+ Rake::Task['teaspoon:tests'].invoke
+end
diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb
index 41df63d445a..8faecec0063 100644
--- a/spec/controllers/projects/labels_controller_spec.rb
+++ b/spec/controllers/projects/labels_controller_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Projects::LabelsController do
let(:group) { create(:group) }
- let(:project) { create(:project, namespace: group) }
+ let(:project) { create(:empty_project, namespace: group) }
let(:user) { create(:user) }
before do
@@ -73,16 +73,27 @@ describe Projects::LabelsController do
describe 'POST #generate' do
let(:admin) { create(:admin) }
- let(:project) { create(:empty_project) }
before do
sign_in(admin)
end
- it 'creates labels' do
- post :generate, namespace_id: project.namespace.to_param, project_id: project.to_param
+ context 'personal project' do
+ let(:personal_project) { create(:empty_project) }
- expect(response).to have_http_status(302)
+ it 'creates labels' do
+ post :generate, namespace_id: personal_project.namespace.to_param, project_id: personal_project.to_param
+
+ expect(response).to have_http_status(302)
+ end
+ end
+
+ context 'project belonging to a group' do
+ it 'creates labels' do
+ post :generate, namespace_id: project.namespace.to_param, project_id: project.to_param
+
+ expect(response).to have_http_status(302)
+ end
end
end
end
diff --git a/spec/javascripts/boards/boards_store_spec.js.es6 b/spec/javascripts/boards/boards_store_spec.js.es6
index 63e487a7ad3..6208c2386b0 100644
--- a/spec/javascripts/boards/boards_store_spec.js.es6
+++ b/spec/javascripts/boards/boards_store_spec.js.es6
@@ -18,7 +18,10 @@
gl.boardService = new BoardService('/test/issue-boards/board', '1');
gl.issueBoards.BoardsStore.create();
- Cookies.set('issue_board_welcome_hidden', 'false');
+ Cookies.set('issue_board_welcome_hidden', 'false', {
+ expires: 365 * 10,
+ path: ''
+ });
});
describe('Store', () => {
diff --git a/spec/javascripts/fixtures/.gitignore b/spec/javascripts/fixtures/.gitignore
new file mode 100644
index 00000000000..009b68d5d1c
--- /dev/null
+++ b/spec/javascripts/fixtures/.gitignore
@@ -0,0 +1 @@
+*.html.raw
diff --git a/spec/javascripts/fixtures/issues.rb b/spec/javascripts/fixtures/issues.rb
new file mode 100644
index 00000000000..d95eb851421
--- /dev/null
+++ b/spec/javascripts/fixtures/issues.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:project) { create(:project_empty_repo) }
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('issues/')
+ end
+
+ before(:each) do
+ sign_in(admin)
+ end
+
+ it 'issues/open-issue.html.raw' do |example|
+ render_issue(example.description, create(:issue, project: project))
+ end
+
+ it 'issues/closed-issue.html.raw' do |example|
+ render_issue(example.description, create(:closed_issue, project: project))
+ end
+
+ it 'issues/issue-with-task-list.html.raw' do |example|
+ issue = create(:issue, project: project)
+ issue.update(description: '- [ ] Task List Item')
+ render_issue(example.description, issue)
+ end
+
+ private
+
+ def render_issue(fixture_file_name, issue)
+ get :show,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: issue.to_param
+
+ expect(response).to be_success
+ store_frontend_fixture(response, fixture_file_name)
+ end
+end
diff --git a/spec/javascripts/fixtures/issues_show.html.haml b/spec/javascripts/fixtures/issues_show.html.haml
deleted file mode 100644
index 06c2ab1e823..00000000000
--- a/spec/javascripts/fixtures/issues_show.html.haml
+++ /dev/null
@@ -1,23 +0,0 @@
-:css
- .hidden { display: none !important; }
-
-.flash-container.flash-container-page
- .flash-alert
- .flash-notice
-
-.status-box.status-box-open Open
-.status-box.status-box-closed.hidden Closed
-%a.btn-close{"href" => "http://gitlab.com/issues/6/close"} Close
-%a.btn-reopen.hidden{"href" => "http://gitlab.com/issues/6/reopen"} Reopen
-
-.detail-page-description
- .description.js-task-list-container
- .wiki
- %ul.task-list
- %li.task-list-item
- %input.task-list-item-checkbox{type: 'checkbox'}
- Task List Item
- %textarea.js-task-list-field
- \- [ ] Task List Item
-
-%form.js-issuable-update{action: '/foo'}
diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js
index c27fb856081..949114185cf 100644
--- a/spec/javascripts/issue_spec.js
+++ b/spec/javascripts/issue_spec.js
@@ -4,116 +4,160 @@
/*= require issue */
(function() {
+ var INVALID_URL = 'http://goesnowhere.nothing/whereami';
+ var $boxClosed, $boxOpen, $btnClose, $btnReopen;
+
+ fixture.preload('issues/closed-issue.html');
+ fixture.preload('issues/issue-with-task-list.html');
+ fixture.preload('issues/open-issue.html');
+
+ function expectErrorMessage() {
+ var $flashMessage = $('div.flash-alert');
+ expect($flashMessage).toExist();
+ expect($flashMessage).toBeVisible();
+ expect($flashMessage).toHaveText('Unable to update this issue at this time.');
+ }
+
+ function expectIssueState(isIssueOpen) {
+ expectVisibility($boxClosed, !isIssueOpen);
+ expectVisibility($boxOpen, isIssueOpen);
+
+ expectVisibility($btnClose, isIssueOpen);
+ expectVisibility($btnReopen, !isIssueOpen);
+ }
+
+ function expectPendingRequest(req, $triggeredButton) {
+ expect(req.type).toBe('PUT');
+ expect(req.url).toBe($triggeredButton.attr('href'));
+ expect($triggeredButton).toHaveProp('disabled', true);
+ }
+
+ function expectVisibility($element, shouldBeVisible) {
+ if (shouldBeVisible) {
+ expect($element).not.toHaveClass('hidden');
+ } else {
+ expect($element).toHaveClass('hidden');
+ }
+ }
+
+ function findElements() {
+ $boxClosed = $('div.status-box-closed');
+ expect($boxClosed).toExist();
+ expect($boxClosed).toHaveText('Closed');
+
+ $boxOpen = $('div.status-box-open');
+ expect($boxOpen).toExist();
+ expect($boxOpen).toHaveText('Open');
+
+ $btnClose = $('.btn-close.btn-grouped');
+ expect($btnClose).toExist();
+ expect($btnClose).toHaveText('Close issue');
+
+ $btnReopen = $('.btn-reopen.btn-grouped');
+ expect($btnReopen).toExist();
+ expect($btnReopen).toHaveText('Reopen issue');
+ }
+
describe('Issue', function() {
- return describe('task lists', function() {
- fixture.preload('issues_show.html');
+ describe('task lists', function() {
+ fixture.load('issues/issue-with-task-list.html');
beforeEach(function() {
- fixture.load('issues_show.html');
- return this.issue = new Issue();
+ this.issue = new Issue();
});
+
it('modifies the Markdown field', function() {
spyOn(jQuery, 'ajax').and.stub();
$('input[type=checkbox]').attr('checked', true).trigger('change');
- return expect($('.js-task-list-field').val()).toBe('- [x] Task List Item');
+ expect($('.js-task-list-field').val()).toBe('- [x] Task List Item');
});
- return it('submits an ajax request on tasklist:changed', function() {
+
+ it('submits an ajax request on tasklist:changed', function() {
spyOn(jQuery, 'ajax').and.callFake(function(req) {
expect(req.type).toBe('PATCH');
- expect(req.url).toBe('/foo');
- return expect(req.data.issue.description).not.toBe(null);
+ expect(req.url).toBe('https://fixture.invalid/namespace3/project3/issues/1.json');
+ expect(req.data.issue.description).not.toBe(null);
});
- return $('.js-task-list-field').trigger('tasklist:changed');
+
+ $('.js-task-list-field').trigger('tasklist:changed');
});
});
});
- describe('reopen/close issue', function() {
- fixture.preload('issues_show.html');
+ describe('close issue', function() {
beforeEach(function() {
- fixture.load('issues_show.html');
- return this.issue = new Issue();
+ fixture.load('issues/open-issue.html');
+ findElements();
+ this.issue = new Issue();
+
+ expectIssueState(true);
});
+
it('closes an issue', function() {
- var $btnClose, $btnReopen;
spyOn(jQuery, 'ajax').and.callFake(function(req) {
- expect(req.type).toBe('PUT');
- expect(req.url).toBe('http://gitlab.com/issues/6/close');
- return req.success({
+ expectPendingRequest(req, $btnClose);
+ req.success({
id: 34
});
});
- $btnClose = $('a.btn-close');
- $btnReopen = $('a.btn-reopen');
- expect($btnReopen).toBeHidden();
- expect($btnClose.text()).toBe('Close');
- expect(typeof $btnClose.prop('disabled')).toBe('undefined');
+
$btnClose.trigger('click');
- expect($btnReopen).toBeVisible();
- expect($btnClose).toBeHidden();
- expect($('div.status-box-closed')).toBeVisible();
- return expect($('div.status-box-open')).toBeHidden();
+
+ expectIssueState(false);
+ expect($btnClose).toHaveProp('disabled', false);
});
+
it('fails to close an issue with success:false', function() {
- var $btnClose, $btnReopen;
spyOn(jQuery, 'ajax').and.callFake(function(req) {
- expect(req.type).toBe('PUT');
- expect(req.url).toBe('http://goesnowhere.nothing/whereami');
- return req.success({
+ expectPendingRequest(req, $btnClose);
+ req.success({
saved: false
});
});
- $btnClose = $('a.btn-close');
- $btnReopen = $('a.btn-reopen');
- $btnClose.attr('href', 'http://goesnowhere.nothing/whereami');
- expect($btnReopen).toBeHidden();
- expect($btnClose.text()).toBe('Close');
- expect(typeof $btnClose.prop('disabled')).toBe('undefined');
+
+ $btnClose.attr('href', INVALID_URL);
$btnClose.trigger('click');
- expect($btnReopen).toBeHidden();
- expect($btnClose).toBeVisible();
- expect($('div.status-box-closed')).toBeHidden();
- expect($('div.status-box-open')).toBeVisible();
- expect($('div.flash-alert')).toBeVisible();
- return expect($('div.flash-alert').text()).toBe('Unable to update this issue at this time.');
+
+ expectIssueState(true);
+ expect($btnClose).toHaveProp('disabled', false);
+ expectErrorMessage();
});
+
it('fails to closes an issue with HTTP error', function() {
- var $btnClose, $btnReopen;
spyOn(jQuery, 'ajax').and.callFake(function(req) {
- expect(req.type).toBe('PUT');
- expect(req.url).toBe('http://goesnowhere.nothing/whereami');
- return req.error();
+ expectPendingRequest(req, $btnClose);
+ req.error();
});
- $btnClose = $('a.btn-close');
- $btnReopen = $('a.btn-reopen');
- $btnClose.attr('href', 'http://goesnowhere.nothing/whereami');
- expect($btnReopen).toBeHidden();
- expect($btnClose.text()).toBe('Close');
- expect(typeof $btnClose.prop('disabled')).toBe('undefined');
+
+ $btnClose.attr('href', INVALID_URL);
$btnClose.trigger('click');
- expect($btnReopen).toBeHidden();
- expect($btnClose).toBeVisible();
- expect($('div.status-box-closed')).toBeHidden();
- expect($('div.status-box-open')).toBeVisible();
- expect($('div.flash-alert')).toBeVisible();
- return expect($('div.flash-alert').text()).toBe('Unable to update this issue at this time.');
+
+ expectIssueState(true);
+ expect($btnClose).toHaveProp('disabled', true);
+ expectErrorMessage();
});
- return it('reopens an issue', function() {
- var $btnClose, $btnReopen;
+ });
+
+ describe('reopen issue', function() {
+ beforeEach(function() {
+ fixture.load('issues/closed-issue.html');
+ findElements();
+ this.issue = new Issue();
+
+ expectIssueState(false);
+ });
+
+ it('reopens an issue', function() {
spyOn(jQuery, 'ajax').and.callFake(function(req) {
- expect(req.type).toBe('PUT');
- expect(req.url).toBe('http://gitlab.com/issues/6/reopen');
- return req.success({
+ expectPendingRequest(req, $btnReopen);
+ req.success({
id: 34
});
});
- $btnClose = $('a.btn-close');
- $btnReopen = $('a.btn-reopen');
- expect($btnReopen.text()).toBe('Reopen');
+
$btnReopen.trigger('click');
- expect($btnReopen).toBeHidden();
- expect($btnClose).toBeVisible();
- expect($('div.status-box-open')).toBeVisible();
- return expect($('div.status-box-closed')).toBeHidden();
+
+ expectIssueState(true);
+ expect($btnReopen).toHaveProp('disabled', false);
});
});
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index feee0f025d8..07a2c316899 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -178,6 +178,7 @@ Ci::Pipeline:
- finished_at
- duration
- user_id
+- lock_version
CommitStatus:
- id
- project_id
@@ -217,6 +218,7 @@ CommitStatus:
- yaml_variables
- queued_at
- token
+- lock_version
Ci::Variable:
- id
- project_id
diff --git a/spec/lib/gitlab/optimistic_locking_spec.rb b/spec/lib/gitlab/optimistic_locking_spec.rb
new file mode 100644
index 00000000000..498dc514c8c
--- /dev/null
+++ b/spec/lib/gitlab/optimistic_locking_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+describe Gitlab::OptimisticLocking, lib: true do
+ describe '#retry_lock' do
+ let!(:pipeline) { create(:ci_pipeline) }
+ let!(:pipeline2) { Ci::Pipeline.find(pipeline.id) }
+
+ it 'does not reload object if state changes' do
+ expect(pipeline).not_to receive(:reload)
+ expect(pipeline).to receive(:succeed).and_call_original
+
+ described_class.retry_lock(pipeline) do |subject|
+ subject.succeed
+ end
+ end
+
+ it 'retries action if exception is raised' do
+ pipeline.succeed
+
+ expect(pipeline2).to receive(:reload).and_call_original
+ expect(pipeline2).to receive(:drop).twice.and_call_original
+
+ described_class.retry_lock(pipeline2) do |subject|
+ subject.drop
+ end
+ end
+
+ it 'raises exception when too many retries' do
+ expect(pipeline).to receive(:drop).twice.and_call_original
+
+ expect do
+ described_class.retry_lock(pipeline, 1) do |subject|
+ subject.lock_version = 100
+ subject.drop
+ end
+ end.to raise_error(ActiveRecord::StaleObjectError)
+ end
+ end
+end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 43397c5ae39..5eb14dc6bd2 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -138,32 +138,26 @@ describe Ci::Pipeline, models: true do
describe 'state machine' do
let(:current) { Time.now.change(usec: 0) }
- let(:build) { create_build('build1', current, 10) }
- let(:build_b) { create_build('build2', current, 20) }
- let(:build_c) { create_build('build3', current + 50, 10) }
+ let(:build) { create_build('build1', 0) }
+ let(:build_b) { create_build('build2', 0) }
+ let(:build_c) { create_build('build3', 0) }
describe '#duration' do
before do
- pipeline.update(created_at: current)
-
- travel_to(current + 5) do
- pipeline.run
- pipeline.save
- end
-
travel_to(current + 30) do
- build.success
+ build.run!
+ build.success!
+ build_b.run!
+ build_c.run!
end
travel_to(current + 40) do
- build_b.drop
+ build_b.drop!
end
travel_to(current + 70) do
- build_c.success
+ build_c.success!
end
-
- pipeline.drop
end
it 'matches sum of builds duration' do
@@ -455,7 +449,9 @@ describe Ci::Pipeline, models: true do
context 'when all builds succeed' do
before do
build_a.success
- build_b.success
+
+ # We have to reload build_b as this is in next stage and it gets triggered by PipelineProcessWorker
+ build_b.reload.success
end
it 'receives a success event once' do
diff --git a/spec/policies/issues_policy_spec.rb b/spec/policies/issues_policy_spec.rb
new file mode 100644
index 00000000000..2b7b6cad654
--- /dev/null
+++ b/spec/policies/issues_policy_spec.rb
@@ -0,0 +1,193 @@
+require 'spec_helper'
+
+describe IssuePolicy, models: true do
+ let(:guest) { create(:user) }
+ let(:author) { create(:user) }
+ let(:assignee) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:group) { create(:group, :public) }
+ let(:reporter_from_group_link) { create(:user) }
+
+ def permissions(user, issue)
+ IssuePolicy.abilities(user, issue).to_set
+ end
+
+ context 'a private project' do
+ let(:non_member) { create(:user) }
+ let(:project) { create(:empty_project, :private) }
+ let(:issue) { create(:issue, project: project, assignee: assignee, author: author) }
+ let(:issue_no_assignee) { create(:issue, project: project) }
+
+ before do
+ project.team << [guest, :guest]
+ project.team << [author, :guest]
+ project.team << [assignee, :guest]
+ project.team << [reporter, :reporter]
+
+ group.add_reporter(reporter_from_group_link)
+
+ create(:project_group_link, group: group, project: project)
+ end
+
+ it 'does not allow non-members to read issues' do
+ expect(permissions(non_member, issue)).not_to include(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(non_member, issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
+ end
+
+ it 'allows guests to read issues' do
+ expect(permissions(guest, issue)).to include(:read_issue)
+ expect(permissions(guest, issue)).not_to include(:update_issue, :admin_issue)
+
+ expect(permissions(guest, issue_no_assignee)).to include(:read_issue)
+ expect(permissions(guest, issue_no_assignee)).not_to include(:update_issue, :admin_issue)
+ end
+
+ it 'allows reporters to read, update, and admin issues' do
+ expect(permissions(reporter, issue)).to include(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(reporter, issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+ end
+
+ it 'allows reporters from group links to read, update, and admin issues' do
+ expect(permissions(reporter_from_group_link, issue)).to include(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(reporter_from_group_link, issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+ end
+
+ it 'allows issue authors to read and update their issues' do
+ expect(permissions(author, issue)).to include(:read_issue, :update_issue)
+ expect(permissions(author, issue)).not_to include(:admin_issue)
+
+ expect(permissions(author, issue_no_assignee)).to include(:read_issue)
+ expect(permissions(author, issue_no_assignee)).not_to include(:update_issue, :admin_issue)
+ end
+
+ it 'allows issue assignees to read and update their issues' do
+ expect(permissions(assignee, issue)).to include(:read_issue, :update_issue)
+ expect(permissions(assignee, issue)).not_to include(:admin_issue)
+
+ expect(permissions(assignee, issue_no_assignee)).to include(:read_issue)
+ expect(permissions(assignee, issue_no_assignee)).not_to include(:update_issue, :admin_issue)
+ end
+
+ context 'with confidential issues' do
+ let(:confidential_issue) { create(:issue, :confidential, project: project, assignee: assignee, author: author) }
+ let(:confidential_issue_no_assignee) { create(:issue, :confidential, project: project) }
+
+ it 'does not allow non-members to read confidential issues' do
+ expect(permissions(non_member, confidential_issue)).not_to include(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(non_member, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
+ end
+
+ it 'does not allow guests to read confidential issues' do
+ expect(permissions(guest, confidential_issue)).not_to include(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(guest, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
+ end
+
+ it 'allows reporters to read, update, and admin confidential issues' do
+ expect(permissions(reporter, confidential_issue)).to include(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(reporter, confidential_issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+ end
+
+ it 'allows reporters from group links to read, update, and admin confidential issues' do
+ expect(permissions(reporter_from_group_link, confidential_issue)).to include(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(reporter_from_group_link, confidential_issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+ end
+
+ it 'allows issue authors to read and update their confidential issues' do
+ expect(permissions(author, confidential_issue)).to include(:read_issue, :update_issue)
+ expect(permissions(author, confidential_issue)).not_to include(:admin_issue)
+
+ expect(permissions(author, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
+ end
+
+ it 'allows issue assignees to read and update their confidential issues' do
+ expect(permissions(assignee, confidential_issue)).to include(:read_issue, :update_issue)
+ expect(permissions(assignee, confidential_issue)).not_to include(:admin_issue)
+
+ expect(permissions(assignee, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
+ end
+ end
+ end
+
+ context 'a public project' do
+ let(:project) { create(:empty_project, :public) }
+ let(:issue) { create(:issue, project: project, assignee: assignee, author: author) }
+ let(:issue_no_assignee) { create(:issue, project: project) }
+
+ before do
+ project.team << [guest, :guest]
+ project.team << [reporter, :reporter]
+
+ group.add_reporter(reporter_from_group_link)
+
+ create(:project_group_link, group: group, project: project)
+ end
+
+ it 'allows guests to read issues' do
+ expect(permissions(guest, issue)).to include(:read_issue)
+ expect(permissions(guest, issue)).not_to include(:update_issue, :admin_issue)
+
+ expect(permissions(guest, issue_no_assignee)).to include(:read_issue)
+ expect(permissions(guest, issue_no_assignee)).not_to include(:update_issue, :admin_issue)
+ end
+
+ it 'allows reporters to read, update, and admin issues' do
+ expect(permissions(reporter, issue)).to include(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(reporter, issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+ end
+
+ it 'allows reporters from group links to read, update, and admin issues' do
+ expect(permissions(reporter_from_group_link, issue)).to include(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(reporter_from_group_link, issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+ end
+
+ it 'allows issue authors to read and update their issues' do
+ expect(permissions(author, issue)).to include(:read_issue, :update_issue)
+ expect(permissions(author, issue)).not_to include(:admin_issue)
+
+ expect(permissions(author, issue_no_assignee)).to include(:read_issue)
+ expect(permissions(author, issue_no_assignee)).not_to include(:update_issue, :admin_issue)
+ end
+
+ it 'allows issue assignees to read and update their issues' do
+ expect(permissions(assignee, issue)).to include(:read_issue, :update_issue)
+ expect(permissions(assignee, issue)).not_to include(:admin_issue)
+
+ expect(permissions(assignee, issue_no_assignee)).to include(:read_issue)
+ expect(permissions(assignee, issue_no_assignee)).not_to include(:update_issue, :admin_issue)
+ end
+
+ context 'with confidential issues' do
+ let(:confidential_issue) { create(:issue, :confidential, project: project, assignee: assignee, author: author) }
+ let(:confidential_issue_no_assignee) { create(:issue, :confidential, project: project) }
+
+ it 'does not allow guests to read confidential issues' do
+ expect(permissions(guest, confidential_issue)).not_to include(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(guest, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
+ end
+
+ it 'allows reporters to read, update, and admin confidential issues' do
+ expect(permissions(reporter, confidential_issue)).to include(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(reporter, confidential_issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+ end
+
+ it 'allows reporter from group links to read, update, and admin confidential issues' do
+ expect(permissions(reporter_from_group_link, confidential_issue)).to include(:read_issue, :update_issue, :admin_issue)
+ expect(permissions(reporter_from_group_link, confidential_issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+ end
+
+ it 'allows issue authors to read and update their confidential issues' do
+ expect(permissions(author, confidential_issue)).to include(:read_issue, :update_issue)
+ expect(permissions(author, confidential_issue)).not_to include(:admin_issue)
+
+ expect(permissions(author, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
+ end
+
+ it 'allows issue assignees to read and update their confidential issues' do
+ expect(permissions(assignee, confidential_issue)).to include(:read_issue, :update_issue)
+ expect(permissions(assignee, confidential_issue)).not_to include(:admin_issue)
+
+ expect(permissions(assignee, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb
index 95c7bbf99c9..fc72a44d663 100644
--- a/spec/requests/api/builds_spec.rb
+++ b/spec/requests/api/builds_spec.rb
@@ -277,6 +277,7 @@ describe API::API, api: true do
context 'with regular branch' do
before do
+ pipeline.reload
pipeline.update(ref: 'master',
sha: project.commit('master').sha)
@@ -288,6 +289,7 @@ describe API::API, api: true do
context 'with branch name containing slash' do
before do
+ pipeline.reload
pipeline.update(ref: 'improve/awesome',
sha: project.commit('improve/awesome').sha)
end
diff --git a/spec/services/ci/register_build_service_spec.rb b/spec/services/ci/register_build_service_spec.rb
index 1e21a32a062..a3fc23ba177 100644
--- a/spec/services/ci/register_build_service_spec.rb
+++ b/spec/services/ci/register_build_service_spec.rb
@@ -101,11 +101,11 @@ module Ci
it 'equalises number of running builds' do
# after finishing the first build for project 1, get a second build from the same project
expect(service.execute(shared_runner)).to eq(build1_project1)
- build1_project1.success
+ build1_project1.reload.success
expect(service.execute(shared_runner)).to eq(build2_project1)
expect(service.execute(shared_runner)).to eq(build1_project2)
- build1_project2.success
+ build1_project2.reload.success
expect(service.execute(shared_runner)).to eq(build2_project2)
expect(service.execute(shared_runner)).to eq(build1_project3)
expect(service.execute(shared_runner)).to eq(build3_project1)
diff --git a/spec/services/labels/find_or_create_service_spec.rb b/spec/services/labels/find_or_create_service_spec.rb
index cbfc63de811..7a9b34f9f96 100644
--- a/spec/services/labels/find_or_create_service_spec.rb
+++ b/spec/services/labels/find_or_create_service_spec.rb
@@ -2,7 +2,6 @@ require 'spec_helper'
describe Labels::FindOrCreateService, services: true do
describe '#execute' do
- let(:user) { create(:user) }
let(:group) { create(:group) }
let(:project) { create(:project, namespace: group) }
@@ -14,37 +13,49 @@ describe Labels::FindOrCreateService, services: true do
}
end
- subject(:service) { described_class.new(user, project, params) }
-
- before do
- project.team << [user, :developer]
- end
+ context 'when acting on behalf of a specific user' do
+ let(:user) { create(:user) }
+ subject(:service) { described_class.new(user, project, params) }
+ before do
+ project.team << [user, :developer]
+ end
- context 'when label does not exist at group level' do
- it 'creates a new label at project level' do
- expect { service.execute }.to change(project.labels, :count).by(1)
+ context 'when label does not exist at group level' do
+ it 'creates a new label at project level' do
+ expect { service.execute }.to change(project.labels, :count).by(1)
+ end
end
- end
- context 'when label exists at group level' do
- it 'returns the group label' do
- group_label = create(:group_label, group: group, title: 'Security')
+ context 'when label exists at group level' do
+ it 'returns the group label' do
+ group_label = create(:group_label, group: group, title: 'Security')
- expect(service.execute).to eq group_label
+ expect(service.execute).to eq group_label
+ end
end
- end
- context 'when label does not exist at group level' do
- it 'creates a new label at project leve' do
- expect { service.execute }.to change(project.labels, :count).by(1)
+ context 'when label does not exist at group level' do
+ it 'creates a new label at project leve' do
+ expect { service.execute }.to change(project.labels, :count).by(1)
+ end
+ end
+
+ context 'when label exists at project level' do
+ it 'returns the project label' do
+ project_label = create(:label, project: project, title: 'Security')
+
+ expect(service.execute).to eq project_label
+ end
end
end
- context 'when label exists at project level' do
+ context 'when authorization is not required' do
+ subject(:service) { described_class.new(nil, project, params) }
+
it 'returns the project label' do
project_label = create(:label, project: project, title: 'Security')
- expect(service.execute).to eq project_label
+ expect(service.execute(skip_authorization: true)).to eq project_label
end
end
end
diff --git a/spec/services/members/approve_access_request_service_spec.rb b/spec/services/members/approve_access_request_service_spec.rb
index 03e296259f9..7b090343a3e 100644
--- a/spec/services/members/approve_access_request_service_spec.rb
+++ b/spec/services/members/approve_access_request_service_spec.rb
@@ -5,36 +5,37 @@ describe Members::ApproveAccessRequestService, services: true do
let(:access_requester) { create(:user) }
let(:project) { create(:project, :public) }
let(:group) { create(:group, :public) }
+ let(:opts) { {} }
shared_examples 'a service raising ActiveRecord::RecordNotFound' do
it 'raises ActiveRecord::RecordNotFound' do
- expect { described_class.new(source, user, params).execute }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { described_class.new(source, user, params).execute(opts) }.to raise_error(ActiveRecord::RecordNotFound)
end
end
shared_examples 'a service raising Gitlab::Access::AccessDeniedError' do
it 'raises Gitlab::Access::AccessDeniedError' do
- expect { described_class.new(source, user, params).execute }.to raise_error(Gitlab::Access::AccessDeniedError)
+ expect { described_class.new(source, user, params).execute(opts) }.to raise_error(Gitlab::Access::AccessDeniedError)
end
end
shared_examples 'a service approving an access request' do
it 'succeeds' do
- expect { described_class.new(source, user, params).execute }.to change { source.requesters.count }.by(-1)
+ expect { described_class.new(source, user, params).execute(opts) }.to change { source.requesters.count }.by(-1)
end
it 'returns a <Source>Member' do
- member = described_class.new(source, user, params).execute
+ member = described_class.new(source, user, params).execute(opts)
expect(member).to be_a "#{source.class}Member".constantize
expect(member.requested_at).to be_nil
end
context 'with a custom access level' do
- let(:params) { { user_id: access_requester.id, access_level: Gitlab::Access::MASTER } }
+ let(:params2) { params.merge(user_id: access_requester.id, access_level: Gitlab::Access::MASTER) }
it 'returns a ProjectMember with the custom access level' do
- member = described_class.new(source, user, params).execute
+ member = described_class.new(source, user, params2).execute(opts)
expect(member.access_level).to eq Gitlab::Access::MASTER
end
@@ -60,6 +61,56 @@ describe Members::ApproveAccessRequestService, services: true do
end
let(:params) { { user_id: access_requester.id } }
+ context 'when current user is nil' do
+ let(:user) { nil }
+
+ context 'and :force option is not given' do
+ it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
+ let(:source) { project }
+ end
+
+ it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
+ let(:source) { group }
+ end
+ end
+
+ context 'and :force option is false' do
+ let(:opts) { { force: false } }
+
+ it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
+ let(:source) { project }
+ end
+
+ it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
+ let(:source) { group }
+ end
+ end
+
+ context 'and :force option is true' do
+ let(:opts) { { force: true } }
+
+ it_behaves_like 'a service approving an access request' do
+ let(:source) { project }
+ end
+
+ it_behaves_like 'a service approving an access request' do
+ let(:source) { group }
+ end
+ end
+
+ context 'and :force param is true' do
+ let(:params) { { user_id: access_requester.id, force: true } }
+
+ it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
+ let(:source) { project }
+ end
+
+ it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
+ let(:source) { group }
+ end
+ end
+ end
+
context 'when current user cannot approve access request to the project' do
it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
let(:source) { project }
diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb
index b80cfd8f450..1f90efdbd6a 100644
--- a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb
+++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb
@@ -147,6 +147,7 @@ describe MergeRequests::MergeWhenBuildSucceedsService do
expect(MergeWorker).not_to receive(:perform_async)
build.success
+ test.reload
test.drop
end
@@ -154,6 +155,7 @@ describe MergeRequests::MergeWhenBuildSucceedsService do
expect(MergeWorker).to receive(:perform_async)
build.success
+ test.reload
test.success
end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 06d52f0f735..b2ca856f89f 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -9,7 +9,7 @@ require 'shoulda/matchers'
require 'sidekiq/testing/inline'
require 'rspec/retry'
-if ENV['CI']
+if ENV['CI'] && !ENV['NO_KNAPSACK']
require 'knapsack'
Knapsack::Adapters::RSpecAdapter.bind
end
diff --git a/spec/support/javascript_fixtures_helpers.rb b/spec/support/javascript_fixtures_helpers.rb
new file mode 100644
index 00000000000..adc3f48b434
--- /dev/null
+++ b/spec/support/javascript_fixtures_helpers.rb
@@ -0,0 +1,45 @@
+require 'fileutils'
+require 'gitlab/popen'
+
+module JavaScriptFixturesHelpers
+ include Gitlab::Popen
+
+ FIXTURE_PATH = 'spec/javascripts/fixtures'
+
+ # Public: Removes all fixture files from given directory
+ #
+ # directory_name - directory of the fixtures (relative to FIXTURE_PATH)
+ #
+ def clean_frontend_fixtures(directory_name)
+ directory_name = File.expand_path(directory_name, FIXTURE_PATH)
+ Dir[File.expand_path('*.html.raw', directory_name)].each do |file_name|
+ FileUtils.rm(file_name)
+ end
+ end
+
+ # Public: Store a response object as fixture file
+ #
+ # response - response object to store
+ # fixture_file_name - file name to store the fixture in (relative to FIXTURE_PATH)
+ #
+ def store_frontend_fixture(response, fixture_file_name)
+ fixture_file_name = File.expand_path(fixture_file_name, FIXTURE_PATH)
+ fixture = response.body
+
+ response_mime_type = Mime::Type.lookup(response.content_type)
+ if response_mime_type.html?
+ doc = Nokogiri::HTML::DocumentFragment.parse(fixture)
+
+ scripts = doc.css('script')
+ scripts.remove
+
+ fixture = doc.to_html
+
+ # replace relative links
+ fixture.gsub!(%r{="/}, '="https://fixture.invalid/')
+ end
+
+ FileUtils.mkdir_p(File.dirname(fixture_file_name))
+ File.write(fixture_file_name, fixture)
+ end
+end