summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/dashboard/todos_controller_spec.rb7
-rw-r--r--spec/controllers/projects/imports_controller_spec.rb9
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb11
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb12
-rw-r--r--spec/factories/ci/builds.rb5
-rw-r--r--spec/factories/environments.rb5
-rw-r--r--spec/factories/keys.rb4
-rw-r--r--spec/features/gitlab_flavored_markdown_spec.rb12
-rw-r--r--spec/features/issues/award_emoji_spec.rb5
-rw-r--r--spec/features/issues/move_spec.rb4
-rw-r--r--spec/features/issues/spam_issues_spec.rb2
-rw-r--r--spec/features/issues_spec.rb17
-rw-r--r--spec/features/merge_requests/create_new_mr_spec.rb12
-rw-r--r--spec/finders/group_projects_finder_spec.rb70
-rw-r--r--spec/finders/projects_finder_spec.rb128
-rw-r--r--spec/helpers/events_helper_spec.rb27
-rw-r--r--spec/helpers/projects_helper_spec.rb8
-rw-r--r--spec/javascripts/blob/3d_viewer/mesh_object_spec.js42
-rw-r--r--spec/javascripts/blob/pdf/index_spec.js24
-rw-r--r--spec/javascripts/build_spec.js2
-rw-r--r--spec/javascripts/environments/environment_actions_spec.js15
-rw-r--r--spec/javascripts/fixtures/environments/metrics.html.haml64
-rw-r--r--spec/javascripts/issue_show/issue_title_spec.js22
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js51
-rw-r--r--spec/javascripts/merge_request_widget_spec.js7
-rw-r--r--spec/javascripts/monitoring/prometheus_graph_spec.js23
-rw-r--r--spec/javascripts/test_bundle.js1
-rw-r--r--spec/javascripts/vue_pipelines_index/pipelines_actions_spec.js15
-rw-r--r--spec/lib/banzai/filter/markdown_filter_spec.rb19
-rw-r--r--spec/lib/banzai/filter/sanitization_filter_spec.rb7
-rw-r--r--spec/lib/banzai/filter/syntax_highlight_filter_spec.rb6
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb42
-rw-r--r--spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_status_spec.rb13
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb4
-rw-r--r--spec/mailers/previews/notify_preview.rb11
-rw-r--r--spec/models/blob_spec.rb22
-rw-r--r--spec/requests/api/deploy_keys_spec.rb9
-rw-r--r--spec/requests/api/issues_spec.rb376
-rw-r--r--spec/rubocop/cop/migration/remove_concurrent_index_spec.rb41
-rw-r--r--spec/rubocop/cop/migration/remove_index_spec.rb35
-rw-r--r--spec/serializers/build_action_entity_spec.rb4
-rw-r--r--spec/serializers/build_entity_spec.rb4
-rw-r--r--spec/services/merge_requests/build_service_spec.rb42
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb27
-rw-r--r--spec/views/notify/pipeline_failed_email.html.haml_spec.rb54
-rw-r--r--spec/views/notify/pipeline_success_email.html.haml_spec.rb54
-rw-r--r--spec/workers/repository_import_worker_spec.rb2
-rw-r--r--spec/workers/stuck_import_jobs_worker_spec.rb36
49 files changed, 1096 insertions, 318 deletions
diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb
index 71a4a2c43c7..6075259ea99 100644
--- a/spec/controllers/dashboard/todos_controller_spec.rb
+++ b/spec/controllers/dashboard/todos_controller_spec.rb
@@ -35,6 +35,13 @@ describe Dashboard::TodosController do
expect(assigns(:todos).current_page).to eq(last_page)
expect(response).to have_http_status(200)
end
+
+ it 'does not redirect to external sites when provided a host field' do
+ external_host = "www.example.com"
+ get :index, page: (last_page + 1).to_param, host: external_host
+
+ expect(response).to redirect_to(dashboard_todos_path(page: last_page))
+ end
end
end
diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb
index 7c75815f3c4..6724b474179 100644
--- a/spec/controllers/projects/imports_controller_spec.rb
+++ b/spec/controllers/projects/imports_controller_spec.rb
@@ -96,12 +96,19 @@ describe Projects::ImportsController do
}
end
- it 'redirects to params[:to]' do
+ it 'redirects to internal params[:to]' do
get :show, namespace_id: project.namespace.to_param, project_id: project, continue: params
expect(flash[:notice]).to eq params[:notice]
expect(response).to redirect_to params[:to]
end
+
+ it 'does not redirect to external params[:to]' do
+ params[:to] = "//google.com"
+
+ get :show, namespace_id: project.namespace.to_param, project_id: project, continue: params
+ expect(response).not_to redirect_to params[:to]
+ end
end
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 734966d50b2..d5f1d46bf7f 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -83,6 +83,17 @@ describe Projects::IssuesController do
expect(assigns(:issues).current_page).to eq(last_page)
expect(response).to have_http_status(200)
end
+
+ it 'does not redirect to external sites when provided a host field' do
+ external_host = "www.example.com"
+ get :index,
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ page: (last_page + 1).to_param,
+ host: external_host
+
+ expect(response).to redirect_to(namespace_project_issues_path(page: last_page, state: controller.params[:state], scope: controller.params[:scope]))
+ end
end
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 72f41f7209a..99d5583e683 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -176,6 +176,18 @@ describe Projects::MergeRequestsController do
expect(assigns(:merge_requests).current_page).to eq(last_page)
expect(response).to have_http_status(200)
end
+
+ it 'does not redirect to external sites when provided a host field' do
+ external_host = "www.example.com"
+ get :index,
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ state: 'opened',
+ page: (last_page + 1).to_param,
+ host: external_host
+
+ expect(response).to redirect_to(namespace_project_merge_requests_path(page: last_page, state: controller.params[:state], scope: controller.params[:scope]))
+ end
end
context 'when filtering by opened state' do
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 1fe5ed25f71..b62def83ee4 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -192,5 +192,10 @@ FactoryGirl.define do
trait :no_options do
options { {} }
end
+
+ trait :non_playable do
+ status 'created'
+ self.when 'manual'
+ end
end
end
diff --git a/spec/factories/environments.rb b/spec/factories/environments.rb
index 0852dda6b29..3fbf24b5c7d 100644
--- a/spec/factories/environments.rb
+++ b/spec/factories/environments.rb
@@ -32,5 +32,10 @@ FactoryGirl.define do
environment.update_attribute(:deployments, [deployment])
end
end
+
+ trait :non_playable do
+ status 'created'
+ self.when 'manual'
+ end
end
end
diff --git a/spec/factories/keys.rb b/spec/factories/keys.rb
index dd93b439b2b..4e140102492 100644
--- a/spec/factories/keys.rb
+++ b/spec/factories/keys.rb
@@ -23,5 +23,9 @@ FactoryGirl.define do
factory :another_deploy_key, class: 'DeployKey' do
end
end
+
+ factory :write_access_key, class: 'DeployKey' do
+ can_push true
+ end
end
end
diff --git a/spec/features/gitlab_flavored_markdown_spec.rb b/spec/features/gitlab_flavored_markdown_spec.rb
index 84d73d693bc..876f33dd03e 100644
--- a/spec/features/gitlab_flavored_markdown_spec.rb
+++ b/spec/features/gitlab_flavored_markdown_spec.rb
@@ -48,7 +48,9 @@ describe "GitLab Flavored Markdown", feature: true do
end
end
- describe "for issues" do
+ describe "for issues", feature: true, js: true do
+ include WaitForVueResource
+
before do
@other_issue = create(:issue,
author: @user,
@@ -79,6 +81,14 @@ describe "GitLab Flavored Markdown", feature: true do
expect(page).to have_link(fred.to_reference)
end
+
+ it "renders updated subject once edited somewhere else in issues#show" do
+ visit namespace_project_issue_path(project.namespace, project, @issue)
+ @issue.update(title: "fix #{@other_issue.to_reference} and update")
+
+ wait_for_vue_resource
+ expect(page).to have_text("fix #{@other_issue.to_reference} and update")
+ end
end
describe "for merge requests" do
diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb
index 16e453bc328..8e67ab028d7 100644
--- a/spec/features/issues/award_emoji_spec.rb
+++ b/spec/features/issues/award_emoji_spec.rb
@@ -2,6 +2,7 @@ require 'rails_helper'
describe 'Awards Emoji', feature: true do
include WaitForAjax
+ include WaitForVueResource
let!(:project) { create(:project, :public) }
let!(:user) { create(:user) }
@@ -22,10 +23,11 @@ describe 'Awards Emoji', feature: true do
# The `heart_tip` emoji is not valid anymore so we need to skip validation
issue.award_emoji.build(user: user, name: 'heart_tip').save!(validate: false)
visit namespace_project_issue_path(project.namespace, project, issue)
+ wait_for_vue_resource
end
# Regression test: https://gitlab.com/gitlab-org/gitlab-ce/issues/29529
- it 'does not shows a 500 page' do
+ it 'does not shows a 500 page', js: true do
expect(page).to have_text(issue.title)
end
end
@@ -35,6 +37,7 @@ describe 'Awards Emoji', feature: true do
before do
visit namespace_project_issue_path(project.namespace, project, issue)
+ wait_for_vue_resource
end
it 'increments the thumbsdown emoji', js: true do
diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb
index f89b4db9e62..6c09903a2f6 100644
--- a/spec/features/issues/move_spec.rb
+++ b/spec/features/issues/move_spec.rb
@@ -37,8 +37,8 @@ feature 'issue move to another project' do
edit_issue(issue)
end
- scenario 'moving issue to another project' do
- first('#move_to_project_id', visible: false).set(new_project.id)
+ scenario 'moving issue to another project', js: true do
+ find('#move_to_project_id', visible: false).set(new_project.id)
click_button('Save changes')
expect(current_url).to include project_path(new_project)
diff --git a/spec/features/issues/spam_issues_spec.rb b/spec/features/issues/spam_issues_spec.rb
index 4bc9b49f889..6001476d0ca 100644
--- a/spec/features/issues/spam_issues_spec.rb
+++ b/spec/features/issues/spam_issues_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe 'New issue', feature: true do
+describe 'New issue', feature: true, js: true do
include StubENV
let(:project) { create(:project, :public) }
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 7afceb88cf9..e3213d24f6a 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -695,4 +695,21 @@ describe 'Issues', feature: true do
end
end
end
+
+ describe 'title issue#show', js: true do
+ include WaitForVueResource
+
+ it 'updates the title', js: true do
+ issue = create(:issue, author: @user, assignee: @user, project: project, title: 'new title')
+
+ visit namespace_project_issue_path(project.namespace, project, issue)
+
+ expect(page).to have_text("new title")
+
+ issue.update(title: "updated title")
+
+ wait_for_vue_resource
+ expect(page).to have_text("updated title")
+ end
+ end
end
diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb
index f36781167fb..d4fe67c224f 100644
--- a/spec/features/merge_requests/create_new_mr_spec.rb
+++ b/spec/features/merge_requests/create_new_mr_spec.rb
@@ -70,6 +70,18 @@ feature 'Create New Merge Request', feature: true, js: true do
visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_project_id: private_project.id })
expect(page).not_to have_content private_project.path_with_namespace
+ expect(page).to have_content project.path_with_namespace
+ end
+ end
+
+ context 'when source project cannot be viewed by the current user' do
+ it 'does not leak the private project name & namespace' do
+ private_project = create(:project, :private)
+
+ visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { source_project_id: private_project.id })
+
+ expect(page).not_to have_content private_project.path_with_namespace
+ expect(page).to have_content project.path_with_namespace
end
end
diff --git a/spec/finders/group_projects_finder_spec.rb b/spec/finders/group_projects_finder_spec.rb
index ef97b061ca7..3c7c9bdcd08 100644
--- a/spec/finders/group_projects_finder_spec.rb
+++ b/spec/finders/group_projects_finder_spec.rb
@@ -3,8 +3,9 @@ require 'spec_helper'
describe GroupProjectsFinder do
let(:group) { create(:group) }
let(:current_user) { create(:user) }
+ let(:options) { {} }
- let(:finder) { described_class.new(source_user) }
+ let(:finder) { described_class.new(group: group, current_user: current_user, options: options) }
let!(:public_project) { create(:empty_project, :public, group: group, path: '1') }
let!(:private_project) { create(:empty_project, :private, group: group, path: '2') }
@@ -18,22 +19,27 @@ describe GroupProjectsFinder do
shared_project_3.project_group_links.create(group_access: Gitlab::Access::MASTER, group: group)
end
+ subject { finder.execute }
+
describe 'with a group member current user' do
- before { group.add_user(current_user, Gitlab::Access::MASTER) }
+ before do
+ group.add_master(current_user)
+ end
context "only shared" do
- subject { described_class.new(group, only_shared: true).execute(current_user) }
- it { is_expected.to eq([shared_project_3, shared_project_2, shared_project_1]) }
+ let(:options) { { only_shared: true } }
+
+ it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1]) }
end
context "only owned" do
- subject { described_class.new(group, only_owned: true).execute(current_user) }
- it { is_expected.to eq([private_project, public_project]) }
+ let(:options) { { only_owned: true } }
+
+ it { is_expected.to match_array([private_project, public_project]) }
end
context "all" do
- subject { described_class.new(group).execute(current_user) }
- it { is_expected.to eq([shared_project_3, shared_project_2, shared_project_1, private_project, public_project]) }
+ it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, private_project, public_project]) }
end
end
@@ -44,47 +50,57 @@ describe GroupProjectsFinder do
end
context "only shared" do
+ let(:options) { { only_shared: true } }
+
context "without external user" do
- subject { described_class.new(group, only_shared: true).execute(current_user) }
- it { is_expected.to eq([shared_project_3, shared_project_2, shared_project_1]) }
+ it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1]) }
end
context "with external user" do
- before { current_user.update_attributes(external: true) }
- subject { described_class.new(group, only_shared: true).execute(current_user) }
- it { is_expected.to eq([shared_project_2, shared_project_1]) }
+ before do
+ current_user.update_attributes(external: true)
+ end
+
+ it { is_expected.to match_array([shared_project_2, shared_project_1]) }
end
end
context "only owned" do
+ let(:options) { { only_owned: true } }
+
context "without external user" do
- before { private_project.team << [current_user, Gitlab::Access::MASTER] }
- subject { described_class.new(group, only_owned: true).execute(current_user) }
- it { is_expected.to eq([private_project, public_project]) }
+ before do
+ private_project.team << [current_user, Gitlab::Access::MASTER]
+ end
+
+ it { is_expected.to match_array([private_project, public_project]) }
end
context "with external user" do
- before { current_user.update_attributes(external: true) }
- subject { described_class.new(group, only_owned: true).execute(current_user) }
- it { is_expected.to eq([public_project]) }
- end
+ before do
+ current_user.update_attributes(external: true)
+ end
- context "all" do
- subject { described_class.new(group).execute(current_user) }
- it { is_expected.to eq([shared_project_3, shared_project_2, shared_project_1, public_project]) }
+ it { is_expected.to eq([public_project]) }
end
end
+
+ context "all" do
+ it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, public_project]) }
+ end
end
describe "no user" do
context "only shared" do
- subject { described_class.new(group, only_shared: true).execute(current_user) }
- it { is_expected.to eq([shared_project_3, shared_project_1]) }
+ let(:options) { { only_shared: true } }
+
+ it { is_expected.to match_array([shared_project_3, shared_project_1]) }
end
context "only owned" do
- subject { described_class.new(group, only_owned: true).execute(current_user) }
- it { is_expected.to eq([public_project]) }
+ let(:options) { { only_owned: true } }
+
+ it { is_expected.to eq([public_project]) }
end
end
end
diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb
index e44e7434c80..148adcffe3b 100644
--- a/spec/finders/projects_finder_spec.rb
+++ b/spec/finders/projects_finder_spec.rb
@@ -21,38 +21,144 @@ describe ProjectsFinder do
create(:empty_project, :private, name: 'D', path: 'D')
end
- let(:finder) { described_class.new }
+ let(:params) { {} }
+ let(:current_user) { user }
+ let(:project_ids_relation) { nil }
+ let(:finder) { described_class.new(params: params, current_user: current_user, project_ids_relation: project_ids_relation) }
+
+ subject { finder.execute }
describe 'without a user' do
- subject { finder.execute }
+ let(:current_user) { nil }
it { is_expected.to eq([public_project]) }
end
describe 'with a user' do
- subject { finder.execute(user) }
-
describe 'without private projects' do
- it { is_expected.to eq([public_project, internal_project]) }
+ it { is_expected.to match_array([public_project, internal_project]) }
end
describe 'with private projects' do
before do
- private_project.add_user(user, Gitlab::Access::MASTER)
+ private_project.add_master(user)
end
- it do
- is_expected.to eq([public_project, internal_project, private_project])
- end
+ it { is_expected.to match_array([public_project, internal_project, private_project]) }
end
end
describe 'with project_ids_relation' do
let(:project_ids_relation) { Project.where(id: internal_project.id) }
- subject { finder.execute(user, project_ids_relation) }
-
it { is_expected.to eq([internal_project]) }
end
+
+ describe 'filter by visibility_level' do
+ before do
+ private_project.add_master(user)
+ end
+
+ context 'private' do
+ let(:params) { { visibility_level: Gitlab::VisibilityLevel::PRIVATE } }
+
+ it { is_expected.to eq([private_project]) }
+ end
+
+ context 'internal' do
+ let(:params) { { visibility_level: Gitlab::VisibilityLevel::INTERNAL } }
+
+ it { is_expected.to eq([internal_project]) }
+ end
+
+ context 'public' do
+ let(:params) { { visibility_level: Gitlab::VisibilityLevel::PUBLIC } }
+
+ it { is_expected.to eq([public_project]) }
+ end
+ end
+
+ describe 'filter by tags' do
+ before do
+ public_project.tag_list.add('foo')
+ public_project.save!
+ end
+
+ let(:params) { { tag: 'foo' } }
+
+ it { is_expected.to eq([public_project]) }
+ end
+
+ describe 'filter by personal' do
+ let!(:personal_project) { create(:empty_project, namespace: user.namespace) }
+ let(:params) { { personal: true } }
+
+ it { is_expected.to eq([personal_project]) }
+ end
+
+ describe 'filter by search' do
+ let(:params) { { search: 'C' } }
+
+ it { is_expected.to eq([public_project]) }
+ end
+
+ describe 'filter by name for backward compatibility' do
+ let(:params) { { name: 'C' } }
+
+ it { is_expected.to eq([public_project]) }
+ end
+
+ describe 'filter by archived' do
+ let!(:archived_project) { create(:empty_project, :public, :archived, name: 'E', path: 'E') }
+
+ context 'non_archived=true' do
+ let(:params) { { non_archived: true } }
+
+ it { is_expected.to match_array([public_project, internal_project]) }
+ end
+
+ context 'non_archived=false' do
+ let(:params) { { non_archived: false } }
+
+ it { is_expected.to match_array([public_project, internal_project, archived_project]) }
+ end
+
+ describe 'filter by archived for backward compatibility' do
+ let(:params) { { archived: false } }
+
+ it { is_expected.to match_array([public_project, internal_project]) }
+ end
+ end
+
+ describe 'filter by trending' do
+ let!(:trending_project) { create(:trending_project, project: public_project) }
+ let(:params) { { trending: true } }
+
+ it { is_expected.to eq([public_project]) }
+ end
+
+ describe 'filter by non_public' do
+ let(:params) { { non_public: true } }
+ before do
+ private_project.add_developer(current_user)
+ end
+
+ it { is_expected.to eq([private_project]) }
+ end
+
+ describe 'filter by viewable_starred_projects' do
+ let(:params) { { starred: true } }
+ before do
+ current_user.toggle_star(public_project)
+ end
+
+ it { is_expected.to eq([public_project]) }
+ end
+
+ describe 'sorting' do
+ let(:params) { { sort: 'name_asc' } }
+
+ it { is_expected.to eq([internal_project, public_project]) }
+ end
end
end
diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb
index 70443d27f33..a7c3c281083 100644
--- a/spec/helpers/events_helper_spec.rb
+++ b/spec/helpers/events_helper_spec.rb
@@ -2,8 +2,10 @@ require 'spec_helper'
describe EventsHelper do
describe '#event_note' do
+ let(:user) { build(:user) }
+
before do
- allow(helper).to receive(:current_user).and_return(double)
+ allow(helper).to receive(:current_user).and_return(user)
end
it 'displays one line of plain text without alteration' do
@@ -60,11 +62,26 @@ describe EventsHelper do
expect(helper.event_note(input)).to eq(expected)
end
- it 'preserves style attribute within a tag' do
- input = '<span class="" style="background-color: #44ad8e; color: #FFFFFF;"></span>'
- expected = '<p><span style="background-color: #44ad8e; color: #FFFFFF;"></span></p>'
+ context 'labels formatting' do
+ let(:input) { 'this should be ~label_1' }
- expect(helper.event_note(input)).to eq(expected)
+ def format_event_note(project)
+ create(:label, title: 'label_1', project: project)
+
+ helper.event_note(input, { project: project })
+ end
+
+ it 'preserves style attribute for a label that can be accessed by current_user' do
+ project = create(:empty_project, :public)
+
+ expect(format_event_note(project)).to match(/span class=.*style=.*/)
+ end
+
+ it 'does not style a label that can not be accessed by current_user' do
+ project = create(:empty_project, :private)
+
+ expect(format_event_note(project)).to eq("<p>#{input}</p>")
+ end
end
end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index fc6ad6419ac..44312ada438 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -167,6 +167,7 @@ describe ProjectsHelper do
before do
allow(project).to receive(:repository_storage_path).and_return('/base/repo/path')
+ allow(Settings.shared).to receive(:[]).with('path').and_return('/base/repo/export/path')
end
it 'removes the repo path' do
@@ -175,6 +176,13 @@ describe ProjectsHelper do
expect(sanitize_repo_path(project, import_error)).to eq('Could not clone [REPOS PATH]/namespace/test.git')
end
+
+ it 'removes the temporary repo path used for uploads/exports' do
+ repo = '/base/repo/export/path/tmp/project_exports/uploads/test.tar.gz'
+ import_error = "Unable to decompress #{repo}\n"
+
+ expect(sanitize_repo_path(project, import_error)).to eq('Unable to decompress [REPO EXPORT PATH]/uploads/test.tar.gz')
+ end
end
describe '#last_push_event' do
diff --git a/spec/javascripts/blob/3d_viewer/mesh_object_spec.js b/spec/javascripts/blob/3d_viewer/mesh_object_spec.js
new file mode 100644
index 00000000000..d1ebae33dab
--- /dev/null
+++ b/spec/javascripts/blob/3d_viewer/mesh_object_spec.js
@@ -0,0 +1,42 @@
+import {
+ BoxGeometry,
+} from 'three/build/three.module';
+import MeshObject from '~/blob/3d_viewer/mesh_object';
+
+describe('Mesh object', () => {
+ it('defaults to non-wireframe material', () => {
+ const object = new MeshObject(
+ new BoxGeometry(10, 10, 10),
+ );
+
+ expect(object.material.wireframe).toBeFalsy();
+ });
+
+ it('changes to wirefame material', () => {
+ const object = new MeshObject(
+ new BoxGeometry(10, 10, 10),
+ );
+
+ object.changeMaterial('wireframe');
+
+ expect(object.material.wireframe).toBeTruthy();
+ });
+
+ it('scales object down', () => {
+ const object = new MeshObject(
+ new BoxGeometry(10, 10, 10),
+ );
+ const radius = object.geometry.boundingSphere.radius;
+
+ expect(radius).not.toBeGreaterThan(4);
+ });
+
+ it('does not scale object down', () => {
+ const object = new MeshObject(
+ new BoxGeometry(1, 1, 1),
+ );
+ const radius = object.geometry.boundingSphere.radius;
+
+ expect(radius).toBeLessThan(1);
+ });
+});
diff --git a/spec/javascripts/blob/pdf/index_spec.js b/spec/javascripts/blob/pdf/index_spec.js
index 19a4e55a9db..d3a4d04345b 100644
--- a/spec/javascripts/blob/pdf/index_spec.js
+++ b/spec/javascripts/blob/pdf/index_spec.js
@@ -3,6 +3,18 @@ import testPDF from './test.pdf';
describe('PDF renderer', () => {
let viewer;
+ let app;
+
+ const checkLoaded = (done) => {
+ if (app.loading) {
+ setTimeout(() => {
+ checkLoaded(done);
+ }, 100);
+ } else {
+ done();
+ }
+ };
+
preloadFixtures('static/pdf_viewer.html.raw');
beforeEach(() => {
@@ -21,11 +33,9 @@ describe('PDF renderer', () => {
describe('successful response', () => {
beforeEach((done) => {
- renderPDF();
+ app = renderPDF();
- setTimeout(() => {
- done();
- }, 500);
+ checkLoaded(done);
});
it('does not show loading icon', () => {
@@ -50,11 +60,9 @@ describe('PDF renderer', () => {
describe('error getting file', () => {
beforeEach((done) => {
viewer.dataset.endpoint = 'invalid/endpoint';
- renderPDF();
+ app = renderPDF();
- setTimeout(() => {
- done();
- }, 500);
+ checkLoaded(done);
});
it('does not show loading icon', () => {
diff --git a/spec/javascripts/build_spec.js b/spec/javascripts/build_spec.js
index a3b6b0235cb..cb48f14fd9a 100644
--- a/spec/javascripts/build_spec.js
+++ b/spec/javascripts/build_spec.js
@@ -77,6 +77,7 @@ describe('Build', () => {
);
expect(dataType).toBe('json');
expect(success).toEqual(jasmine.any(Function));
+ spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {});
success.call(context, { html: '<span>Example</span>', status: 'running' });
@@ -85,6 +86,7 @@ describe('Build', () => {
it('removes the spinner', () => {
const [{ success, context }] = $.ajax.calls.argsFor(0);
+ spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {});
success.call(context, { trace_html: '<span>Example</span>', status: 'success' });
expect($('.js-build-refresh').length).toBe(0);
diff --git a/spec/javascripts/environments/environment_actions_spec.js b/spec/javascripts/environments/environment_actions_spec.js
index 13840b42bd6..6348d97b0a5 100644
--- a/spec/javascripts/environments/environment_actions_spec.js
+++ b/spec/javascripts/environments/environment_actions_spec.js
@@ -19,6 +19,11 @@ describe('Actions Component', () => {
name: 'foo',
play_path: '#',
},
+ {
+ name: 'foo bar',
+ play_path: 'url',
+ playable: false,
+ },
];
spy = jasmine.createSpy('spy').and.returnValue(Promise.resolve());
@@ -49,4 +54,14 @@ describe('Actions Component', () => {
expect(spy).toHaveBeenCalledWith(actionsMock[0].play_path);
});
+
+ it('should render a disabled action when it\'s not playable', () => {
+ expect(
+ component.$el.querySelector('.dropdown-menu li:last-child button').getAttribute('disabled'),
+ ).toEqual('disabled');
+
+ expect(
+ component.$el.querySelector('.dropdown-menu li:last-child button').classList.contains('disabled'),
+ ).toEqual(true);
+ });
});
diff --git a/spec/javascripts/fixtures/environments/metrics.html.haml b/spec/javascripts/fixtures/environments/metrics.html.haml
index 483063fb889..e2dd9519898 100644
--- a/spec/javascripts/fixtures/environments/metrics.html.haml
+++ b/spec/javascripts/fixtures/environments/metrics.html.haml
@@ -1,12 +1,62 @@
-%div
+.prometheus-container{ 'data-has-metrics': "false", 'data-doc-link': '/help/administration/monitoring/prometheus/index.md', 'data-prometheus-integration': '/root/hello-prometheus/services/prometheus/edit' }
.top-area
.row
.col-sm-6
%h3.page-title
Metrics for environment
- .row
- .col-sm-12
- %svg.prometheus-graph{ 'graph-type' => 'cpu_values' }
- .row
- .col-sm-12
- %svg.prometheus-graph{ 'graph-type' => 'memory_values' } \ No newline at end of file
+ .prometheus-state
+ .js-getting-started.hidden
+ .row
+ .col-md-4.col-md-offset-4.state-svg
+ %svg
+ .row
+ .col-md-6.col-md-offset-3
+ %h4.text-center.state-title
+ Get started with performance monitoring
+ .row
+ .col-md-6.col-md-offset-3
+ .description-text.text-center.state-description
+ Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments. Learn more about performance monitoring
+ .row.state-button-section
+ .col-md-4.col-md-offset-4.text-center.state-button
+ %a.btn.btn-success
+ Configure Prometheus
+ .js-loading.hidden
+ .row
+ .col-md-4.col-md-offset-4.state-svg
+ %svg
+ .row
+ .col-md-6.col-md-offset-3
+ %h4.text-center.state-title
+ Waiting for performance data
+ .row
+ .col-md-6.col-md-offset-3
+ .description-text.text-center.state-description
+ Creating graphs uses the data from the Prometheus server. If this takes a long time, ensure that data is available.
+ .row.state-button-section
+ .col-md-4.col-md-offset-4.text-center.state-button
+ %a.btn.btn-success
+ View documentation
+ .js-unable-to-connect.hidden
+ .row
+ .col-md-4.col-md-offset-4.state-svg
+ %svg
+ .row
+ .col-md-6.col-md-offset-3
+ %h4.text-center.state-title
+ Unable to connect to Prometheus server
+ .row
+ .col-md-6.col-md-offset-3
+ .description-text.text-center.state-description
+ Ensure connectivity is available from the GitLab server to the Prometheus server
+ .row.state-button-section
+ .col-md-4.col-md-offset-4.text-center.state-button
+ %a.btn.btn-success
+ View documentation
+ .prometheus-graphs
+ .row
+ .col-sm-12
+ %svg.prometheus-graph{ 'graph-type' => 'cpu_values' }
+ .row
+ .col-sm-12
+ %svg.prometheus-graph{ 'graph-type' => 'memory_values' }
diff --git a/spec/javascripts/issue_show/issue_title_spec.js b/spec/javascripts/issue_show/issue_title_spec.js
new file mode 100644
index 00000000000..806d728a874
--- /dev/null
+++ b/spec/javascripts/issue_show/issue_title_spec.js
@@ -0,0 +1,22 @@
+import Vue from 'vue';
+import issueTitle from '~/issue_show/issue_title';
+
+describe('Issue Title', () => {
+ let IssueTitleComponent;
+
+ beforeEach(() => {
+ IssueTitleComponent = Vue.extend(issueTitle);
+ });
+
+ it('should render a title', () => {
+ const component = new IssueTitleComponent({
+ propsData: {
+ initialTitle: 'wow',
+ endpoint: '/gitlab-org/gitlab-shell/issues/9/rendered_title',
+ },
+ }).$mount();
+
+ expect(component.$el.classList).toContain('title');
+ expect(component.$el.innerHTML).toContain('wow');
+ });
+});
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index 5a93d479c1f..03f3c206f44 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -310,5 +310,56 @@ require('~/lib/utils/common_utils');
});
}, 10000);
});
+
+ describe('gl.utils.setFavicon', () => {
+ it('should set page favicon to provided favicon', () => {
+ const faviconName = 'custom_favicon';
+ const fakeLink = {
+ setAttribute() {},
+ };
+
+ spyOn(window.document, 'getElementById').and.callFake(() => fakeLink);
+ spyOn(fakeLink, 'setAttribute').and.callFake((attr, val) => {
+ expect(attr).toEqual('href');
+ expect(val.indexOf('/assets/custom_favicon.ico') > -1).toBe(true);
+ });
+ gl.utils.setFavicon(faviconName);
+ });
+ });
+
+ describe('gl.utils.resetFavicon', () => {
+ it('should reset page favicon to tanuki', () => {
+ const fakeLink = {
+ setAttribute() {},
+ };
+
+ spyOn(window.document, 'getElementById').and.callFake(() => fakeLink);
+ spyOn(fakeLink, 'setAttribute').and.callFake((attr, val) => {
+ expect(attr).toEqual('href');
+ expect(val).toMatch(/favicon/);
+ });
+ gl.utils.resetFavicon();
+ });
+ });
+
+ describe('gl.utils.setCiStatusFavicon', () => {
+ it('should set page favicon to CI status favicon based on provided status', () => {
+ const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/builds/1/status.json`;
+ const FAVICON_PATH = 'ci_favicons/';
+ const FAVICON = 'icon_status_success';
+ const spySetFavicon = spyOn(gl.utils, 'setFavicon').and.stub();
+ const spyResetFavicon = spyOn(gl.utils, 'resetFavicon').and.stub();
+ spyOn($, 'ajax').and.callFake(function (options) {
+ options.success({ icon: FAVICON });
+ expect(spySetFavicon).toHaveBeenCalledWith(FAVICON_PATH + FAVICON);
+ options.success();
+ expect(spyResetFavicon).toHaveBeenCalled();
+ options.error();
+ expect(spyResetFavicon).toHaveBeenCalled();
+ });
+
+ gl.utils.setCiStatusFavicon(BUILD_URL);
+ });
+ });
});
})();
diff --git a/spec/javascripts/merge_request_widget_spec.js b/spec/javascripts/merge_request_widget_spec.js
index d5193b41c33..88dae8c3e06 100644
--- a/spec/javascripts/merge_request_widget_spec.js
+++ b/spec/javascripts/merge_request_widget_spec.js
@@ -142,18 +142,21 @@ require('~/lib/utils/datetime_utility');
it('should call showCIStatus even if a notification should not be displayed', function() {
var spy;
spy = spyOn(this["class"], 'showCIStatus').and.stub();
+ spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {});
this["class"].getCIStatus(false);
return expect(spy).toHaveBeenCalledWith(this.ciStatusData.status);
});
it('should call showCIStatus when a notification should be displayed', function() {
var spy;
spy = spyOn(this["class"], 'showCIStatus').and.stub();
+ spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {});
this["class"].getCIStatus(true);
return expect(spy).toHaveBeenCalledWith(this.ciStatusData.status);
});
it('should call showCICoverage when the coverage rate is set', function() {
var spy;
spy = spyOn(this["class"], 'showCICoverage').and.stub();
+ spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {});
this["class"].getCIStatus(false);
return expect(spy).toHaveBeenCalledWith(this.ciStatusData.coverage);
});
@@ -161,12 +164,14 @@ require('~/lib/utils/datetime_utility');
var spy;
this.ciStatusData.coverage = null;
spy = spyOn(this["class"], 'showCICoverage').and.stub();
+ spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {});
this["class"].getCIStatus(false);
return expect(spy).not.toHaveBeenCalled();
});
it('should not display a notification on the first check after the widget has been created', function() {
var spy;
spy = spyOn(window, 'notify');
+ spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {});
this["class"] = new window.gl.MergeRequestWidget(this.opts);
this["class"].getCIStatus(true);
return expect(spy).not.toHaveBeenCalled();
@@ -174,6 +179,7 @@ require('~/lib/utils/datetime_utility');
it('should update the pipeline URL when the pipeline changes', function() {
var spy;
spy = spyOn(this["class"], 'updatePipelineUrls').and.stub();
+ spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {});
this["class"].getCIStatus(false);
this.ciStatusData.pipeline += 1;
this["class"].getCIStatus(false);
@@ -182,6 +188,7 @@ require('~/lib/utils/datetime_utility');
it('should update the commit URL when the sha changes', function() {
var spy;
spy = spyOn(this["class"], 'updateCommitUrls').and.stub();
+ spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {});
this["class"].getCIStatus(false);
this.ciStatusData.sha = "9b50b99a";
this["class"].getCIStatus(false);
diff --git a/spec/javascripts/monitoring/prometheus_graph_spec.js b/spec/javascripts/monitoring/prometheus_graph_spec.js
index c2bcd9c0f7c..4b904fc2960 100644
--- a/spec/javascripts/monitoring/prometheus_graph_spec.js
+++ b/spec/javascripts/monitoring/prometheus_graph_spec.js
@@ -1,5 +1,4 @@
import 'jquery';
-import '~/lib/utils/common_utils';
import PrometheusGraph from '~/monitoring/prometheus_graph';
import { prometheusMockData } from './prometheus_mock_data';
@@ -12,6 +11,7 @@ describe('PrometheusGraph', () => {
beforeEach(() => {
loadFixtures(fixtureName);
+ $('.prometheus-container').data('has-metrics', 'true');
this.prometheusGraph = new PrometheusGraph();
const self = this;
const fakeInit = (metricsResponse) => {
@@ -75,3 +75,24 @@ describe('PrometheusGraph', () => {
});
});
});
+
+describe('PrometheusGraphs UX states', () => {
+ const fixtureName = 'static/environments/metrics.html.raw';
+ preloadFixtures(fixtureName);
+
+ beforeEach(() => {
+ loadFixtures(fixtureName);
+ this.prometheusGraph = new PrometheusGraph();
+ });
+
+ it('shows a specified state', () => {
+ this.prometheusGraph.state = '.js-getting-started';
+ this.prometheusGraph.updateState();
+ const $state = $('.js-getting-started');
+ expect($state).toBeDefined();
+ expect($('.state-title', $state)).toBeDefined();
+ expect($('.state-svg', $state)).toBeDefined();
+ expect($('.state-description', $state)).toBeDefined();
+ expect($('.state-button', $state)).toBeDefined();
+ });
+});
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index b30c5da8822..07dc51a7815 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -64,6 +64,7 @@ if (process.env.BABEL_ENV === 'coverage') {
'./snippet/snippet_bundle.js',
'./terminal/terminal_bundle.js',
'./users/users_bundle.js',
+ './issue_show/index.js',
];
describe('Uncovered files', function () {
diff --git a/spec/javascripts/vue_pipelines_index/pipelines_actions_spec.js b/spec/javascripts/vue_pipelines_index/pipelines_actions_spec.js
index dba998c7688..0910df61915 100644
--- a/spec/javascripts/vue_pipelines_index/pipelines_actions_spec.js
+++ b/spec/javascripts/vue_pipelines_index/pipelines_actions_spec.js
@@ -15,6 +15,11 @@ describe('Pipelines Actions dropdown', () => {
name: 'stop_review',
path: '/root/review-app/builds/1893/play',
},
+ {
+ name: 'foo',
+ path: '#',
+ playable: false,
+ },
];
spy = jasmine.createSpy('spy').and.returnValue(Promise.resolve());
@@ -59,4 +64,14 @@ describe('Pipelines Actions dropdown', () => {
expect(component.$el.querySelector('.fa-spinner')).toEqual(null);
});
+
+ it('should render a disabled action when it\'s not playable', () => {
+ expect(
+ component.$el.querySelector('.dropdown-menu li:last-child button').getAttribute('disabled'),
+ ).toEqual('disabled');
+
+ expect(
+ component.$el.querySelector('.dropdown-menu li:last-child button').classList.contains('disabled'),
+ ).toEqual(true);
+ });
});
diff --git a/spec/lib/banzai/filter/markdown_filter_spec.rb b/spec/lib/banzai/filter/markdown_filter_spec.rb
new file mode 100644
index 00000000000..897288b8ad5
--- /dev/null
+++ b/spec/lib/banzai/filter/markdown_filter_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe Banzai::Filter::MarkdownFilter, lib: true do
+ include FilterSpecHelper
+
+ context 'code block' do
+ it 'adds language to lang attribute when specified' do
+ result = filter("```html\nsome code\n```")
+
+ expect(result).to start_with("\n<pre><code lang=\"html\">")
+ end
+
+ it 'does not add language to lang attribute when not specified' do
+ result = filter("```\nsome code\n```")
+
+ expect(result).to start_with("\n<pre><code>")
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb
index b4cd5f63a15..fdbc65b5e00 100644
--- a/spec/lib/banzai/filter/sanitization_filter_spec.rb
+++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb
@@ -49,11 +49,12 @@ describe Banzai::Filter::SanitizationFilter, lib: true do
instance = described_class.new('Foo')
3.times { instance.whitelist }
- expect(instance.whitelist[:transformers].size).to eq 5
+ expect(instance.whitelist[:transformers].size).to eq 4
end
- it 'allows syntax highlighting' do
- exp = act = %q{<pre class="code highlight white c"><code><span class="k">def</span></code></pre>}
+ it 'sanitizes `class` attribute from all elements' do
+ act = %q{<pre class="code highlight white c"><code>&lt;span class="k"&gt;def&lt;/span&gt;</code></pre>}
+ exp = %q{<pre><code>&lt;span class="k"&gt;def&lt;/span&gt;</code></pre>}
expect(filter(act).to_html).to eq exp
end
diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
index 63fb1bb25c4..f61fc8ceb9e 100644
--- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
+++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
@@ -12,14 +12,14 @@ describe Banzai::Filter::SyntaxHighlightFilter, lib: true do
context "when a valid language is specified" do
it "highlights as that language" do
- result = filter('<pre><code class="ruby">def fun end</code></pre>')
+ result = filter('<pre><code lang="ruby">def fun end</code></pre>')
expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight ruby" lang="ruby" v-pre="true"><code><span id="LC1" class="line" lang="ruby"><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></span></code></pre>')
end
end
context "when an invalid language is specified" do
it "highlights as plaintext" do
- result = filter('<pre><code class="gnuplot">This is a test</code></pre>')
+ result = filter('<pre><code lang="gnuplot">This is a test</code></pre>')
expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre>')
end
end
@@ -30,7 +30,7 @@ describe Banzai::Filter::SyntaxHighlightFilter, lib: true do
end
it "highlights as plaintext" do
- result = filter('<pre><code class="ruby">This is a test</code></pre>')
+ result = filter('<pre><code lang="ruby">This is a test</code></pre>')
expect(result.to_html).to eq('<pre class="code highlight" lang="" v-pre="true"><code>This is a test</code></pre>')
end
end
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index e007044868c..4ac79454647 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -58,6 +58,48 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
end
end
+ describe '#remove_concurrent_index' do
+ context 'outside a transaction' do
+ before do
+ allow(model).to receive(:transaction_open?).and_return(false)
+ end
+
+ context 'using PostgreSQL' do
+ before do
+ allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
+ allow(model).to receive(:disable_statement_timeout)
+ end
+
+ it 'removes the index concurrently' do
+ expect(model).to receive(:remove_index).
+ with(:users, { algorithm: :concurrently, column: :foo })
+
+ model.remove_concurrent_index(:users, :foo)
+ end
+ end
+
+ context 'using MySQL' do
+ it 'removes an index' do
+ expect(Gitlab::Database).to receive(:postgresql?).and_return(false)
+
+ expect(model).to receive(:remove_index).
+ with(:users, { column: :foo })
+
+ model.remove_concurrent_index(:users, :foo)
+ end
+ end
+ end
+
+ context 'inside a transaction' do
+ it 'raises RuntimeError' do
+ expect(model).to receive(:transaction_open?).and_return(true)
+
+ expect { model.remove_concurrent_index(:users, :foo) }.
+ to raise_error(RuntimeError)
+ end
+ end
+ end
+
describe '#add_concurrent_foreign_key' do
context 'inside a transaction' do
it 'raises an error' do
diff --git a/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb b/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb
index 287bf62d9bd..6307f8c16a3 100644
--- a/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb
+++ b/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe Gitlab::SidekiqStatus::ClientMiddleware do
describe '#call' do
it 'tracks the job in Redis' do
- expect(Gitlab::SidekiqStatus).to receive(:set).with('123')
+ expect(Gitlab::SidekiqStatus).to receive(:set).with('123', Gitlab::SidekiqStatus::DEFAULT_EXPIRATION)
described_class.new.
call('Foo', { 'jid' => '123' }, double(:queue), double(:pool)) { nil }
diff --git a/spec/lib/gitlab/sidekiq_status_spec.rb b/spec/lib/gitlab/sidekiq_status_spec.rb
index 56f06b61afb..496e50fbae4 100644
--- a/spec/lib/gitlab/sidekiq_status_spec.rb
+++ b/spec/lib/gitlab/sidekiq_status_spec.rb
@@ -73,4 +73,17 @@ describe Gitlab::SidekiqStatus do
expect(key).to include('123')
end
end
+
+ describe 'completed', :redis do
+ it 'returns the completed job' do
+ expect(described_class.completed_jids(%w(123))).to eq(['123'])
+ end
+
+ it 'returns only the jobs completed' do
+ described_class.set('123')
+ described_class.set('456')
+
+ expect(described_class.completed_jids(%w(123 456 789))).to eq(['789'])
+ end
+ end
end
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index 9fbcb1fee69..b703e9808a8 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -188,10 +188,8 @@ describe Gitlab::Workhorse, lib: true do
context 'when Gitaly is enabled' do
let(:gitaly_params) do
- address = Gitlab::GitalyClient.get_address('default')
{
- GitalySocketPath: URI(address).path,
- GitalyAddress: address,
+ GitalyAddress: Gitlab::GitalyClient.get_address('default'),
}
end
diff --git a/spec/mailers/previews/notify_preview.rb b/spec/mailers/previews/notify_preview.rb
new file mode 100644
index 00000000000..0e1ccb5b847
--- /dev/null
+++ b/spec/mailers/previews/notify_preview.rb
@@ -0,0 +1,11 @@
+class NotifyPreview < ActionMailer::Preview
+ def pipeline_success_email
+ pipeline = Ci::Pipeline.last
+ Notify.pipeline_success_email(pipeline, pipeline.user.try(:email))
+ end
+
+ def pipeline_failed_email
+ pipeline = Ci::Pipeline.last
+ Notify.pipeline_failed_email(pipeline, pipeline.user.try(:email))
+ end
+end
diff --git a/spec/models/blob_spec.rb b/spec/models/blob_spec.rb
index 09b1fda3796..0f29766db41 100644
--- a/spec/models/blob_spec.rb
+++ b/spec/models/blob_spec.rb
@@ -111,6 +111,20 @@ describe Blob do
end
end
+ describe '#stl?' do
+ it 'is falsey with image extension' do
+ git_blob = Gitlab::Git::Blob.new(name: 'file.png')
+
+ expect(described_class.decorate(git_blob)).not_to be_stl
+ end
+
+ it 'is truthy with STL extension' do
+ git_blob = Gitlab::Git::Blob.new(name: 'file.stl')
+
+ expect(described_class.decorate(git_blob)).to be_stl
+ end
+ end
+
describe '#to_partial_path' do
let(:project) { double(lfs_enabled?: true) }
@@ -122,7 +136,8 @@ describe Blob do
lfs_pointer?: false,
svg?: false,
text?: false,
- binary?: false
+ binary?: false,
+ stl?: false
)
described_class.decorate(double).tap do |blob|
@@ -175,6 +190,11 @@ describe Blob do
blob = stubbed_blob(text?: true, sketch?: true, binary?: true)
expect(blob.to_partial_path(project)).to eq 'sketch'
end
+
+ it 'handles STLs' do
+ blob = stubbed_blob(text?: true, stl?: true)
+ expect(blob.to_partial_path(project)).to eq 'stl'
+ end
end
describe '#size_within_svg_limits?' do
diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb
index 4f4b18cf0e0..e1beac28dab 100644
--- a/spec/requests/api/deploy_keys_spec.rb
+++ b/spec/requests/api/deploy_keys_spec.rb
@@ -108,6 +108,15 @@ describe API::DeployKeys, api: true do
expect(response).to have_http_status(201)
end
+
+ it 'accepts can_push parameter' do
+ key_attrs = attributes_for :write_access_key
+
+ post api("/projects/#{project.id}/deploy_keys", admin), key_attrs
+
+ expect(response).to have_http_status(201)
+ expect(json_response['can_push']).to eq(true)
+ end
end
describe 'DELETE /projects/:id/deploy_keys/:key_id' do
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 4729adba11c..2b27ce6390a 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -12,6 +12,8 @@ describe API::Issues, api: true do
let(:assignee) { create(:assignee) }
let(:admin) { create(:user, :admin) }
let!(:project) { create(:empty_project, :public, creator_id: user.id, namespace: user.namespace ) }
+ let(:issue_title) { 'foo' }
+ let(:issue_description) { 'closed' }
let!(:closed_issue) do
create :closed_issue,
author: user,
@@ -38,7 +40,9 @@ describe API::Issues, api: true do
project: project,
milestone: milestone,
created_at: generate(:past_time),
- updated_at: 1.hour.ago
+ updated_at: 1.hour.ago,
+ title: issue_title,
+ description: issue_description
end
let!(:label) do
create(:label, title: 'label', color: '#FFAABB', project: project)
@@ -61,6 +65,7 @@ describe API::Issues, api: true do
context "when unauthenticated" do
it "returns authentication error" do
get api("/issues")
+
expect(response).to have_http_status(401)
end
end
@@ -69,9 +74,7 @@ describe API::Issues, api: true do
it "returns an array of issues" do
get api("/issues", user)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
+ expect_paginated_array_response(size: 2)
expect(json_response.first['title']).to eq(issue.title)
expect(json_response.last).to have_key('web_url')
end
@@ -79,41 +82,43 @@ describe API::Issues, api: true do
it 'returns an array of closed issues' do
get api('/issues?state=closed', user)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+ expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(closed_issue.id)
end
it 'returns an array of opened issues' do
get api('/issues?state=opened', user)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+ expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(issue.id)
end
it 'returns an array of all issues' do
get api('/issues?state=all', user)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(2)
+ expect_paginated_array_response(size: 2)
expect(json_response.first['id']).to eq(issue.id)
expect(json_response.second['id']).to eq(closed_issue.id)
end
+ it 'returns issues matching given search string for title' do
+ get api("/issues?search=#{issue.title}", user)
+
+ expect_paginated_array_response(size: 1)
+ expect(json_response.first['id']).to eq(issue.id)
+ end
+
+ it 'returns issues matching given search string for description' do
+ get api("/issues?search=#{issue.description}", user)
+
+ expect_paginated_array_response(size: 1)
+ expect(json_response.first['id']).to eq(issue.id)
+ end
+
it 'returns an array of labeled issues' do
get api("/issues?labels=#{label.title}", user)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+ expect_paginated_array_response(size: 1)
expect(json_response.first['labels']).to eq([label.title])
end
@@ -126,29 +131,20 @@ describe API::Issues, api: true do
get api("/issues", user), labels: "#{label.title},#{label_b.title},#{label_c.title}"
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+ expect_paginated_array_response(size: 1)
expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title])
end
it 'returns an empty array if no issue matches labels' do
get api('/issues?labels=foo,bar', user)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(0)
+ expect_paginated_array_response(size: 0)
end
it 'returns an array of labeled issues matching given state' do
get api("/issues?labels=#{label.title}&state=opened", user)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+ expect_paginated_array_response(size: 1)
expect(json_response.first['labels']).to eq([label.title])
expect(json_response.first['state']).to eq('opened')
end
@@ -156,47 +152,32 @@ describe API::Issues, api: true do
it 'returns unlabeled issues for "No Label" label' do
get api("/issues", user), labels: 'No Label'
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+ expect_paginated_array_response(size: 1)
expect(json_response.first['labels']).to be_empty
end
it 'returns an empty array if no issue matches labels and state filters' do
get api("/issues?labels=#{label.title}&state=closed", user)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(0)
+ expect_paginated_array_response(size: 0)
end
it 'returns an empty array if no issue matches milestone' do
get api("/issues?milestone=#{empty_milestone.title}", user)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(0)
+ expect_paginated_array_response(size: 0)
end
it 'returns an empty array if milestone does not exist' do
get api("/issues?milestone=foo", user)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(0)
+ expect_paginated_array_response(size: 0)
end
it 'returns an array of issues in given milestone' do
get api("/issues?milestone=#{milestone.title}", user)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(2)
+ expect_paginated_array_response(size: 2)
expect(json_response.first['id']).to eq(issue.id)
expect(json_response.second['id']).to eq(closed_issue.id)
end
@@ -205,49 +186,36 @@ describe API::Issues, api: true do
get api("/issues?milestone=#{milestone.title}"\
'&state=closed', user)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+ expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(closed_issue.id)
end
it 'returns an array of issues with no milestone' do
get api("/issues?milestone=#{no_milestone_title}", author)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+ expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(confidential_issue.id)
end
it 'returns an array of issues found by iids' do
get api('/issues', user), iids: [closed_issue.iid]
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+ expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(closed_issue.id)
end
it 'returns an empty array if iid does not exist' do
get api("/issues", user), iids: [99999]
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(0)
+ expect_paginated_array_response(size: 0)
end
it 'sorts by created_at descending by default' do
get api('/issues', user)
response_dates = json_response.map { |issue| issue['created_at'] }
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
+
+ expect_paginated_array_response(size: 2)
expect(response_dates).to eq(response_dates.sort.reverse)
end
@@ -255,9 +223,8 @@ describe API::Issues, api: true do
get api('/issues?sort=asc', user)
response_dates = json_response.map { |issue| issue['created_at'] }
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
+
+ expect_paginated_array_response(size: 2)
expect(response_dates).to eq(response_dates.sort)
end
@@ -265,9 +232,8 @@ describe API::Issues, api: true do
get api('/issues?order_by=updated_at', user)
response_dates = json_response.map { |issue| issue['updated_at'] }
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
+
+ expect_paginated_array_response(size: 2)
expect(response_dates).to eq(response_dates.sort.reverse)
end
@@ -275,9 +241,8 @@ describe API::Issues, api: true do
get api('/issues?order_by=updated_at&sort=asc', user)
response_dates = json_response.map { |issue| issue['updated_at'] }
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
+
+ expect_paginated_array_response(size: 2)
expect(response_dates).to eq(response_dates.sort)
end
@@ -316,7 +281,9 @@ describe API::Issues, api: true do
assignee: user,
project: group_project,
milestone: group_milestone,
- updated_at: 1.hour.ago
+ updated_at: 1.hour.ago,
+ title: issue_title,
+ description: issue_description
end
let!(:group_label) do
create(:label, title: 'group_lbl', color: '#FFAABB', project: group_project)
@@ -336,74 +303,65 @@ describe API::Issues, api: true do
it 'returns all group issues (including opened and closed)' do
get api(base_url, admin)
- expect(response).to have_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(3)
+ expect_paginated_array_response(size: 3)
end
it 'returns group issues without confidential issues for non project members' do
get api("#{base_url}?state=opened", non_member)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+ expect_paginated_array_response(size: 1)
expect(json_response.first['title']).to eq(group_issue.title)
end
it 'returns group confidential issues for author' do
get api("#{base_url}?state=opened", author)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(2)
+ expect_paginated_array_response(size: 2)
end
it 'returns group confidential issues for assignee' do
get api("#{base_url}?state=opened", assignee)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(2)
+ expect_paginated_array_response(size: 2)
end
it 'returns group issues with confidential issues for project members' do
get api("#{base_url}?state=opened", user)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(2)
+ expect_paginated_array_response(size: 2)
end
it 'returns group confidential issues for admin' do
get api("#{base_url}?state=opened", admin)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(2)
+ expect_paginated_array_response(size: 2)
end
it 'returns an array of labeled group issues' do
get api("#{base_url}?labels=#{group_label.title}", user)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+ expect_paginated_array_response(size: 1)
expect(json_response.first['labels']).to eq([group_label.title])
end
it 'returns an array of labeled group issues where all labels match' do
get api("#{base_url}?labels=#{group_label.title},foo,bar", user)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(0)
+ expect_paginated_array_response(size: 0)
+ end
+
+ it 'returns issues matching given search string for title' do
+ get api("#{base_url}?search=#{group_issue.title}", user)
+
+ expect_paginated_array_response(size: 1)
+ expect(json_response.first['id']).to eq(group_issue.id)
+ end
+
+ it 'returns issues matching given search string for description' do
+ get api("#{base_url}?search=#{group_issue.description}", user)
+
+ expect_paginated_array_response(size: 1)
+ expect(json_response.first['id']).to eq(group_issue.id)
end
it 'returns an array of labeled issues when all labels matches' do
@@ -415,65 +373,45 @@ describe API::Issues, api: true do
get api("#{base_url}", user), labels: "#{group_label.title},#{label_b.title},#{label_c.title}"
- expect(response).to have_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+ expect_paginated_array_response(size: 1)
expect(json_response.first['labels']).to eq([label_c.title, label_b.title, group_label.title])
end
it 'returns an array of issues found by iids' do
get api(base_url, user), iids: [group_issue.iid]
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+ expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(group_issue.id)
end
it 'returns an empty array if iid does not exist' do
get api(base_url, user), iids: [99999]
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(0)
+ expect_paginated_array_response(size: 0)
end
it 'returns an empty array if no group issue matches labels' do
get api("#{base_url}?labels=foo,bar", user)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(0)
+ expect_paginated_array_response(size: 0)
end
it 'returns an empty array if no issue matches milestone' do
get api("#{base_url}?milestone=#{group_empty_milestone.title}", user)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(0)
+ expect_paginated_array_response(size: 0)
end
it 'returns an empty array if milestone does not exist' do
get api("#{base_url}?milestone=foo", user)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(0)
+ expect_paginated_array_response(size: 0)
end
it 'returns an array of issues in given milestone' do
get api("#{base_url}?state=opened&milestone=#{group_milestone.title}", user)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+ expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(group_issue.id)
end
@@ -481,10 +419,7 @@ describe API::Issues, api: true do
get api("#{base_url}?milestone=#{group_milestone.title}"\
'&state=closed', user)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+ expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(group_closed_issue.id)
end
@@ -492,9 +427,8 @@ describe API::Issues, api: true do
get api("#{base_url}?milestone=#{no_milestone_title}", user)
expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+
+ expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(group_confidential_issue.id)
end
@@ -502,9 +436,8 @@ describe API::Issues, api: true do
get api(base_url, user)
response_dates = json_response.map { |issue| issue['created_at'] }
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
+
+ expect_paginated_array_response(size: 3)
expect(response_dates).to eq(response_dates.sort.reverse)
end
@@ -512,9 +445,8 @@ describe API::Issues, api: true do
get api("#{base_url}?sort=asc", user)
response_dates = json_response.map { |issue| issue['created_at'] }
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
+
+ expect_paginated_array_response(size: 3)
expect(response_dates).to eq(response_dates.sort)
end
@@ -522,9 +454,8 @@ describe API::Issues, api: true do
get api("#{base_url}?order_by=updated_at", user)
response_dates = json_response.map { |issue| issue['updated_at'] }
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
+
+ expect_paginated_array_response(size: 3)
expect(response_dates).to eq(response_dates.sort.reverse)
end
@@ -532,9 +463,8 @@ describe API::Issues, api: true do
get api("#{base_url}?order_by=updated_at&sort=asc", user)
response_dates = json_response.map { |issue| issue['updated_at'] }
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
+
+ expect_paginated_array_response(size: 3)
expect(response_dates).to eq(response_dates.sort)
end
end
@@ -563,79 +493,55 @@ describe API::Issues, api: true do
get api("/projects/#{restricted_project.id}/issues", non_member)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response).to eq([])
+ expect_paginated_array_response(size: 0)
end
it 'returns project issues without confidential issues for non project members' do
get api("#{base_url}/issues", non_member)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(2)
+ expect_paginated_array_response(size: 2)
expect(json_response.first['title']).to eq(issue.title)
end
it 'returns project issues without confidential issues for project members with guest role' do
get api("#{base_url}/issues", guest)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(2)
+ expect_paginated_array_response(size: 2)
expect(json_response.first['title']).to eq(issue.title)
end
it 'returns project confidential issues for author' do
get api("#{base_url}/issues", author)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(3)
+ expect_paginated_array_response(size: 3)
expect(json_response.first['title']).to eq(issue.title)
end
it 'returns project confidential issues for assignee' do
get api("#{base_url}/issues", assignee)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(3)
+ expect_paginated_array_response(size: 3)
expect(json_response.first['title']).to eq(issue.title)
end
it 'returns project issues with confidential issues for project members' do
get api("#{base_url}/issues", user)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(3)
+ expect_paginated_array_response(size: 3)
expect(json_response.first['title']).to eq(issue.title)
end
it 'returns project confidential issues for admin' do
get api("#{base_url}/issues", admin)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(3)
+ expect_paginated_array_response(size: 3)
expect(json_response.first['title']).to eq(issue.title)
end
it 'returns an array of labeled project issues' do
get api("#{base_url}/issues?labels=#{label.title}", user)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+ expect_paginated_array_response(size: 1)
expect(json_response.first['labels']).to eq([label.title])
end
@@ -648,74 +554,65 @@ describe API::Issues, api: true do
get api("#{base_url}/issues", user), labels: "#{label.title},#{label_b.title},#{label_c.title}"
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+ expect_paginated_array_response(size: 1)
expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title])
end
+ it 'returns issues matching given search string for title' do
+ get api("#{base_url}/issues?search=#{issue.title}", user)
+
+ expect_paginated_array_response(size: 1)
+ expect(json_response.first['id']).to eq(issue.id)
+ end
+
+ it 'returns issues matching given search string for description' do
+ get api("#{base_url}/issues?search=#{issue.description}", user)
+
+ expect_paginated_array_response(size: 1)
+ expect(json_response.first['id']).to eq(issue.id)
+ end
+
it 'returns an array of issues found by iids' do
get api("#{base_url}/issues", user), iids: [issue.iid]
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+ expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(issue.id)
end
it 'returns an empty array if iid does not exist' do
get api("#{base_url}/issues", user), iids: [99999]
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(0)
+ expect_paginated_array_response(size: 0)
end
it 'returns an empty array if not all labels matches' do
get api("#{base_url}/issues?labels=#{label.title},foo", user)
- expect(response).to have_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(0)
+ expect_paginated_array_response(size: 0)
end
it 'returns an empty array if no project issue matches labels' do
get api("#{base_url}/issues?labels=foo,bar", user)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(0)
+ expect_paginated_array_response(size: 0)
end
it 'returns an empty array if no issue matches milestone' do
get api("#{base_url}/issues?milestone=#{empty_milestone.title}", user)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(0)
+ expect_paginated_array_response(size: 0)
end
it 'returns an empty array if milestone does not exist' do
get api("#{base_url}/issues?milestone=foo", user)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(0)
+ expect_paginated_array_response(size: 0)
end
it 'returns an array of issues in given milestone' do
get api("#{base_url}/issues?milestone=#{milestone.title}", user)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(2)
+ expect_paginated_array_response(size: 2)
expect(json_response.first['id']).to eq(issue.id)
expect(json_response.second['id']).to eq(closed_issue.id)
end
@@ -723,20 +620,14 @@ describe API::Issues, api: true do
it 'returns an array of issues matching state in milestone' do
get api("#{base_url}/issues?milestone=#{milestone.title}&state=closed", user)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+ expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(closed_issue.id)
end
it 'returns an array of issues with no milestone' do
get api("#{base_url}/issues?milestone=#{no_milestone_title}", user)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+ expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(confidential_issue.id)
end
@@ -744,9 +635,8 @@ describe API::Issues, api: true do
get api("#{base_url}/issues", user)
response_dates = json_response.map { |issue| issue['created_at'] }
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
+
+ expect_paginated_array_response(size: 3)
expect(response_dates).to eq(response_dates.sort.reverse)
end
@@ -754,9 +644,8 @@ describe API::Issues, api: true do
get api("#{base_url}/issues?sort=asc", user)
response_dates = json_response.map { |issue| issue['created_at'] }
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
+
+ expect_paginated_array_response(size: 3)
expect(response_dates).to eq(response_dates.sort)
end
@@ -764,9 +653,8 @@ describe API::Issues, api: true do
get api("#{base_url}/issues?order_by=updated_at", user)
response_dates = json_response.map { |issue| issue['updated_at'] }
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
+
+ expect_paginated_array_response(size: 3)
expect(response_dates).to eq(response_dates.sort.reverse)
end
@@ -774,9 +662,8 @@ describe API::Issues, api: true do
get api("#{base_url}/issues?order_by=updated_at&sort=asc", user)
response_dates = json_response.map { |issue| issue['updated_at'] }
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
+
+ expect_paginated_array_response(size: 3)
expect(response_dates).to eq(response_dates.sort)
end
end
@@ -1457,4 +1344,11 @@ describe API::Issues, api: true do
include_examples 'time tracking endpoints', 'issue'
end
+
+ def expect_paginated_array_response(size: nil)
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(size) if size
+ end
end
diff --git a/spec/rubocop/cop/migration/remove_concurrent_index_spec.rb b/spec/rubocop/cop/migration/remove_concurrent_index_spec.rb
new file mode 100644
index 00000000000..a714bf4e5d5
--- /dev/null
+++ b/spec/rubocop/cop/migration/remove_concurrent_index_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../../rubocop/cop/migration/remove_concurrent_index'
+
+describe RuboCop::Cop::Migration::RemoveConcurrentIndex do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context 'in migration' do
+ before do
+ allow(cop).to receive(:in_migration?).and_return(true)
+ end
+
+ it 'registers an offense when remove_concurrent_index is used inside a change method' do
+ inspect_source(cop, 'def change; remove_concurrent_index :table, :column; end')
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ end
+ end
+
+ it 'registers no offense when remove_concurrent_index is used inside an up method' do
+ inspect_source(cop, 'def up; remove_concurrent_index :table, :column; end')
+
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+
+ context 'outside of migration' do
+ it 'registers no offense' do
+ inspect_source(cop, 'def change; remove_concurrent_index :table, :column; end')
+
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+end
diff --git a/spec/rubocop/cop/migration/remove_index_spec.rb b/spec/rubocop/cop/migration/remove_index_spec.rb
new file mode 100644
index 00000000000..31923cb7429
--- /dev/null
+++ b/spec/rubocop/cop/migration/remove_index_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../../rubocop/cop/migration/remove_index'
+
+describe RuboCop::Cop::Migration::RemoveIndex do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context 'in migration' do
+ before do
+ allow(cop).to receive(:in_migration?).and_return(true)
+ end
+
+ it 'registers an offense when remove_index is used' do
+ inspect_source(cop, 'def change; remove_index :table, :column; end')
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ end
+ end
+ end
+
+ context 'outside of migration' do
+ it 'registers no offense' do
+ inspect_source(cop, 'def change; remove_index :table, :column; end')
+
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+end
diff --git a/spec/serializers/build_action_entity_spec.rb b/spec/serializers/build_action_entity_spec.rb
index 0f7be8b2c39..54ac17447b1 100644
--- a/spec/serializers/build_action_entity_spec.rb
+++ b/spec/serializers/build_action_entity_spec.rb
@@ -17,5 +17,9 @@ describe BuildActionEntity do
it 'contains path to the action play' do
expect(subject[:path]).to include "builds/#{build.id}/play"
end
+
+ it 'contains whether it is playable' do
+ expect(subject[:playable]).to eq build.playable?
+ end
end
end
diff --git a/spec/serializers/build_entity_spec.rb b/spec/serializers/build_entity_spec.rb
index 7dcdf54fd93..f76a5cf72d1 100644
--- a/spec/serializers/build_entity_spec.rb
+++ b/spec/serializers/build_entity_spec.rb
@@ -24,6 +24,10 @@ describe BuildEntity do
expect(subject).not_to include(/variables/)
end
+ it 'contains whether it is playable' do
+ expect(subject[:playable]).to eq build.playable?
+ end
+
it 'contains timestamps' do
expect(subject).to include(:created_at, :updated_at)
end
diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb
index c8bd4d4601a..be9f9ea2dec 100644
--- a/spec/services/merge_requests/build_service_spec.rb
+++ b/spec/services/merge_requests/build_service_spec.rb
@@ -4,6 +4,8 @@ describe MergeRequests::BuildService, services: true do
include RepoHelpers
let(:project) { create(:project, :repository) }
+ let(:source_project) { nil }
+ let(:target_project) { nil }
let(:user) { create(:user) }
let(:issue_confidential) { false }
let(:issue) { create(:issue, project: project, title: 'A bug', confidential: issue_confidential) }
@@ -20,7 +22,9 @@ describe MergeRequests::BuildService, services: true do
MergeRequests::BuildService.new(project, user,
description: description,
source_branch: source_branch,
- target_branch: target_branch)
+ target_branch: target_branch,
+ source_project: source_project,
+ target_project: target_project)
end
before do
@@ -256,5 +260,41 @@ describe MergeRequests::BuildService, services: true do
)
end
end
+
+ context 'target_project is set and accessible by current_user' do
+ let(:target_project) { create(:project, :public, :repository)}
+ let(:commits) { Commit.decorate([commit_1], project) }
+
+ it 'sets target project correctly' do
+ expect(merge_request.target_project).to eq(target_project)
+ end
+ end
+
+ context 'target_project is set but not accessible by current_user' do
+ let(:target_project) { create(:project, :private, :repository)}
+ let(:commits) { Commit.decorate([commit_1], project) }
+
+ it 'sets target project correctly' do
+ expect(merge_request.target_project).to eq(project)
+ end
+ end
+
+ context 'source_project is set and accessible by current_user' do
+ let(:source_project) { create(:project, :public, :repository)}
+ let(:commits) { Commit.decorate([commit_1], project) }
+
+ it 'sets target project correctly' do
+ expect(merge_request.source_project).to eq(source_project)
+ end
+ end
+
+ context 'source_project is set but not accessible by current_user' do
+ let(:source_project) { create(:project, :private, :repository)}
+ let(:commits) { Commit.decorate([commit_1], project) }
+
+ it 'sets target project correctly' do
+ expect(merge_request.source_project).to eq(project)
+ end
+ end
end
end
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index c22d145ca5d..03215a4624a 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -49,6 +49,7 @@ describe MergeRequests::RefreshService, services: true do
context 'push to origin repo source branch' do
let(:refresh_service) { service.new(@project, @user) }
+
before do
allow(refresh_service).to receive(:execute_hooks)
refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
@@ -70,6 +71,32 @@ describe MergeRequests::RefreshService, services: true do
end
end
+ context 'push to origin repo source branch when an MR was reopened' do
+ let(:refresh_service) { service.new(@project, @user) }
+
+ before do
+ @merge_request.update(state: :reopened)
+
+ allow(refresh_service).to receive(:execute_hooks)
+ refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
+ reload_mrs
+ end
+
+ it 'executes hooks with update action' do
+ expect(refresh_service).to have_received(:execute_hooks).
+ with(@merge_request, 'update', @oldrev)
+
+ expect(@merge_request.notes).not_to be_empty
+ expect(@merge_request).to be_open
+ expect(@merge_request.merge_when_pipeline_succeeds).to be_falsey
+ expect(@merge_request.diff_head_sha).to eq(@newrev)
+ expect(@fork_merge_request).to be_open
+ expect(@fork_merge_request.notes).to be_empty
+ expect(@build_failed_todo).to be_done
+ expect(@fork_build_failed_todo).to be_done
+ end
+ end
+
context 'push to origin repo target branch' do
before do
service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/feature')
diff --git a/spec/views/notify/pipeline_failed_email.html.haml_spec.rb b/spec/views/notify/pipeline_failed_email.html.haml_spec.rb
new file mode 100644
index 00000000000..f627f9165fb
--- /dev/null
+++ b/spec/views/notify/pipeline_failed_email.html.haml_spec.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+
+describe 'notify/pipeline_failed_email.html.haml' do
+ include Devise::Test::ControllerHelpers
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:merge_request) { create(:merge_request, :simple, source_project: project) }
+
+ let(:pipeline) do
+ create(:ci_pipeline,
+ project: project,
+ user: user,
+ ref: project.default_branch,
+ sha: project.commit.sha,
+ status: :success)
+ end
+
+ before do
+ assign(:project, project)
+ assign(:pipeline, pipeline)
+ assign(:merge_request, merge_request)
+ end
+
+ context 'pipeline with user' do
+ it 'renders the email correctly' do
+ render
+
+ expect(rendered).to have_content "Your pipeline has failed"
+ expect(rendered).to have_content pipeline.project.name
+ expect(rendered).to have_content pipeline.git_commit_message.truncate(50)
+ expect(rendered).to have_content pipeline.commit.author_name
+ expect(rendered).to have_content "##{pipeline.id}"
+ expect(rendered).to have_content pipeline.user.name
+ end
+ end
+
+ context 'pipeline without user' do
+ before do
+ pipeline.update_attribute(:user, nil)
+ end
+
+ it 'renders the email correctly' do
+ render
+
+ expect(rendered).to have_content "Your pipeline has failed"
+ expect(rendered).to have_content pipeline.project.name
+ expect(rendered).to have_content pipeline.git_commit_message.truncate(50)
+ expect(rendered).to have_content pipeline.commit.author_name
+ expect(rendered).to have_content "##{pipeline.id}"
+ expect(rendered).to have_content "by API"
+ end
+ end
+end
diff --git a/spec/views/notify/pipeline_success_email.html.haml_spec.rb b/spec/views/notify/pipeline_success_email.html.haml_spec.rb
new file mode 100644
index 00000000000..ecd096ee579
--- /dev/null
+++ b/spec/views/notify/pipeline_success_email.html.haml_spec.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+
+describe 'notify/pipeline_success_email.html.haml' do
+ include Devise::Test::ControllerHelpers
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:merge_request) { create(:merge_request, :simple, source_project: project) }
+
+ let(:pipeline) do
+ create(:ci_pipeline,
+ project: project,
+ user: user,
+ ref: project.default_branch,
+ sha: project.commit.sha,
+ status: :success)
+ end
+
+ before do
+ assign(:project, project)
+ assign(:pipeline, pipeline)
+ assign(:merge_request, merge_request)
+ end
+
+ context 'pipeline with user' do
+ it 'renders the email correctly' do
+ render
+
+ expect(rendered).to have_content "Your pipeline has passed"
+ expect(rendered).to have_content pipeline.project.name
+ expect(rendered).to have_content pipeline.git_commit_message.truncate(50)
+ expect(rendered).to have_content pipeline.commit.author_name
+ expect(rendered).to have_content "##{pipeline.id}"
+ expect(rendered).to have_content pipeline.user.name
+ end
+ end
+
+ context 'pipeline without user' do
+ before do
+ pipeline.update_attribute(:user, nil)
+ end
+
+ it 'renders the email correctly' do
+ render
+
+ expect(rendered).to have_content "Your pipeline has passed"
+ expect(rendered).to have_content pipeline.project.name
+ expect(rendered).to have_content pipeline.git_commit_message.truncate(50)
+ expect(rendered).to have_content pipeline.commit.author_name
+ expect(rendered).to have_content "##{pipeline.id}"
+ expect(rendered).to have_content "by API"
+ end
+ end
+end
diff --git a/spec/workers/repository_import_worker_spec.rb b/spec/workers/repository_import_worker_spec.rb
index fbb22439f33..5a2c0671dac 100644
--- a/spec/workers/repository_import_worker_spec.rb
+++ b/spec/workers/repository_import_worker_spec.rb
@@ -23,10 +23,12 @@ describe RepositoryImportWorker do
error = %q{remote: Not Found fatal: repository 'https://user:pass@test.com/root/repoC.git/' not found }
expect_any_instance_of(Projects::ImportService).to receive(:execute).
and_return({ status: :error, message: error })
+ allow(subject).to receive(:jid).and_return('123')
subject.perform(project.id)
expect(project.reload.import_error).to include("https://*****:*****@test.com/root/repoC.git/")
+ expect(project.reload.import_jid).not_to be_nil
end
end
end
diff --git a/spec/workers/stuck_import_jobs_worker_spec.rb b/spec/workers/stuck_import_jobs_worker_spec.rb
new file mode 100644
index 00000000000..466277a5e5e
--- /dev/null
+++ b/spec/workers/stuck_import_jobs_worker_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+describe StuckImportJobsWorker do
+ let(:worker) { described_class.new }
+ let(:exclusive_lease_uuid) { SecureRandom.uuid }
+
+ before do
+ allow_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain).and_return(exclusive_lease_uuid)
+ end
+
+ describe 'long running import' do
+ let(:project) { create(:empty_project, import_jid: '123', import_status: 'started') }
+
+ before do
+ allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return(['123'])
+ end
+
+ it 'marks the project as failed' do
+ expect { worker.perform }.to change { project.reload.import_status }.to('failed')
+ end
+ end
+
+ describe 'running import' do
+ let(:project) { create(:empty_project, import_jid: '123', import_status: 'started') }
+
+ before do
+ allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return([])
+ end
+
+ it 'does not mark the project as failed' do
+ worker.perform
+
+ expect(project.reload.import_status).to eq('started')
+ end
+ end
+end