summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/benchmarks/finders/issues_finder_spec.rb55
-rw-r--r--spec/benchmarks/finders/trending_projects_finder_spec.rb14
-rw-r--r--spec/benchmarks/lib/gitlab/markdown/reference_filter_spec.rb41
-rw-r--r--spec/benchmarks/models/milestone_spec.rb17
-rw-r--r--spec/benchmarks/models/project_spec.rb50
-rw-r--r--spec/benchmarks/models/project_team_spec.rb23
-rw-r--r--spec/benchmarks/models/user_spec.rb78
-rw-r--r--spec/benchmarks/services/projects/create_service_spec.rb28
-rw-r--r--spec/controllers/namespaces_controller_spec.rb2
-rw-r--r--spec/controllers/profiles/avatars_controller_spec.rb2
-rw-r--r--spec/controllers/projects/branches_controller_spec.rb96
-rw-r--r--spec/controllers/projects/forks_controller_spec.rb2
-rw-r--r--spec/controllers/projects/imports_controller_spec.rb4
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb162
-rw-r--r--spec/controllers/projects/repositories_controller_spec.rb37
-rw-r--r--spec/controllers/projects_controller_spec.rb13
-rw-r--r--spec/controllers/uploads_controller_spec.rb2
-rw-r--r--spec/factories/issues.rb4
-rw-r--r--spec/factories/labels.rb2
-rw-r--r--spec/factories/merge_requests.rb10
-rw-r--r--spec/factories/personal_snippets.rb11
-rw-r--r--spec/factories/project_group_links.rb6
-rw-r--r--spec/factories/project_snippets.rb6
-rw-r--r--spec/factories/snippets.rb12
-rw-r--r--spec/factories/todos.rb10
-rw-r--r--spec/factories/users.rb7
-rw-r--r--spec/features/issues/filter_by_milestone_spec.rb7
-rw-r--r--spec/features/issues/move_spec.rb87
-rw-r--r--spec/features/issues/new_branch_button_spec.rb49
-rw-r--r--spec/features/merge_requests/filter_by_milestone_spec.rb5
-rw-r--r--spec/features/notes_on_merge_requests_spec.rb6
-rw-r--r--spec/features/security/project/internal_access_spec.rb57
-rw-r--r--spec/features/security/project/private_access_spec.rb52
-rw-r--r--spec/features/security/project/public_access_spec.rb41
-rw-r--r--spec/finders/projects_finder_spec.rb34
-rw-r--r--spec/finders/snippets_finder_spec.rb25
-rw-r--r--spec/helpers/application_helper_spec.rb6
-rw-r--r--spec/helpers/labels_helper_spec.rb12
-rw-r--r--spec/helpers/projects_helper_spec.rb19
-rw-r--r--spec/helpers/visibility_level_helper_spec.rb6
-rw-r--r--spec/lib/banzai/filter/label_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/redactor_filter_spec.rb72
-rw-r--r--spec/lib/banzai/filter/sanitization_filter_spec.rb20
-rw-r--r--spec/lib/ci/gitlab_ci_yaml_processor_spec.rb123
-rw-r--r--spec/lib/ci/status_spec.rb23
-rw-r--r--spec/lib/gitlab/bitbucket_import/importer_spec.rb88
-rw-r--r--spec/lib/gitlab/closing_issue_extractor_spec.rb1
-rw-r--r--spec/lib/gitlab/diff/file_spec.rb14
-rw-r--r--spec/lib/gitlab/diff/parser_spec.rb5
-rw-r--r--spec/lib/gitlab/exclusive_lease_spec.rb21
-rw-r--r--spec/lib/gitlab/gfm/reference_rewriter_spec.rb89
-rw-r--r--spec/lib/gitlab/github_import/pull_request_formatter_spec.rb64
-rw-r--r--spec/lib/gitlab/middleware/go_spec.rb30
-rw-r--r--spec/lib/gitlab/project_search_results_spec.rb69
-rw-r--r--spec/lib/gitlab/reference_extractor_spec.rb22
-rw-r--r--spec/lib/gitlab/search_results_spec.rb144
-rw-r--r--spec/lib/gitlab/snippet_search_results_spec.rb25
-rw-r--r--spec/mailers/emails/profile_spec.rb6
-rw-r--r--spec/mailers/notify_spec.rb83
-rw-r--r--spec/mailers/shared/notify.rb6
-rw-r--r--spec/models/abuse_report_spec.rb16
-rw-r--r--spec/models/build_spec.rb66
-rw-r--r--spec/models/ci/commit_spec.rb44
-rw-r--r--spec/models/ci/runner_spec.rb28
-rw-r--r--spec/models/commit_spec.rb13
-rw-r--r--spec/models/concerns/issuable_spec.rb89
-rw-r--r--spec/models/concerns/mentionable_spec.rb5
-rw-r--r--spec/models/concerns/milestoneish_spec.rb104
-rw-r--r--spec/models/concerns/subscribable_spec.rb57
-rw-r--r--spec/models/event_spec.rb36
-rw-r--r--spec/models/group_spec.rb26
-rw-r--r--spec/models/hooks/service_hook_spec.rb2
-rw-r--r--spec/models/hooks/web_hook_spec.rb2
-rw-r--r--spec/models/issue_spec.rb92
-rw-r--r--spec/models/merge_request_spec.rb82
-rw-r--r--spec/models/milestone_spec.rb51
-rw-r--r--spec/models/namespace_spec.rb29
-rw-r--r--spec/models/note_spec.rb84
-rw-r--r--spec/models/project_group_link_spec.rb17
-rw-r--r--spec/models/project_snippet_spec.rb1
-rw-r--r--spec/models/project_spec.rb117
-rw-r--r--spec/models/project_team_spec.rb44
-rw-r--r--spec/models/project_wiki_spec.rb12
-rw-r--r--spec/models/repository_spec.rb257
-rw-r--r--spec/models/snippet_spec.rb45
-rw-r--r--spec/models/todo_spec.rb85
-rw-r--r--spec/models/user_spec.rb87
-rw-r--r--spec/requests/api/issues_spec.rb112
-rw-r--r--spec/requests/api/project_snippets_spec.rb18
-rw-r--r--spec/requests/api/projects_spec.rb36
-rw-r--r--spec/requests/api/users_spec.rb29
-rw-r--r--spec/services/delete_tag_service_spec.rb26
-rw-r--r--spec/services/delete_user_service_spec.rb58
-rw-r--r--spec/services/git_push_service_spec.rb88
-rw-r--r--spec/services/git_tag_push_service_spec.rb4
-rw-r--r--spec/services/issues/move_service_spec.rb213
-rw-r--r--spec/services/issues/update_service_spec.rb45
-rw-r--r--spec/services/merge_requests/update_service_spec.rb45
-rw-r--r--spec/services/notification_service_spec.rb88
-rw-r--r--spec/services/projects/autocomplete_service_spec.rb79
-rw-r--r--spec/services/projects/housekeeping_service_spec.rb48
-rw-r--r--spec/services/projects/update_service_spec.rb4
-rw-r--r--spec/services/system_note_service_spec.rb65
-rw-r--r--spec/services/todo_service_spec.rb9
-rw-r--r--spec/spec_helper.rb7
-rw-r--r--spec/support/capybara.rb2
-rw-r--r--spec/support/email_helpers.rb13
-rw-r--r--spec/support/matchers/access_matchers.rb2
-rw-r--r--spec/support/matchers/benchmark_matchers.rb61
-rw-r--r--spec/support/mentionable_shared_examples.rb2
-rw-r--r--spec/support/wait_for_ajax.rb2
-rw-r--r--spec/workers/delete_user_worker_spec.rb20
-rw-r--r--spec/workers/post_receive_spec.rb2
-rw-r--r--spec/workers/repository_fork_worker_spec.rb19
114 files changed, 3783 insertions, 812 deletions
diff --git a/spec/benchmarks/finders/issues_finder_spec.rb b/spec/benchmarks/finders/issues_finder_spec.rb
deleted file mode 100644
index b57a33004a4..00000000000
--- a/spec/benchmarks/finders/issues_finder_spec.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-require 'spec_helper'
-
-describe IssuesFinder, benchmark: true do
- describe '#execute' do
- let(:user) { create(:user) }
- let(:project) { create(:project, :public) }
-
- let(:label1) { create(:label, project: project, title: 'A') }
- let(:label2) { create(:label, project: project, title: 'B') }
-
- before do
- 10.times do |n|
- issue = create(:issue, author: user, project: project)
-
- if n > 4
- create(:label_link, label: label1, target: issue)
- create(:label_link, label: label2, target: issue)
- end
- end
- end
-
- describe 'retrieving issues without labels' do
- let(:finder) do
- IssuesFinder.new(user, scope: 'all', label_name: Label::None.title,
- state: 'opened')
- end
-
- benchmark_subject { finder.execute }
-
- it { is_expected.to iterate_per_second(2000) }
- end
-
- describe 'retrieving issues with labels' do
- let(:finder) do
- IssuesFinder.new(user, scope: 'all', label_name: label1.title,
- state: 'opened')
- end
-
- benchmark_subject { finder.execute }
-
- it { is_expected.to iterate_per_second(1000) }
- end
-
- describe 'retrieving issues for a single project' do
- let(:finder) do
- IssuesFinder.new(user, scope: 'all', label_name: Label::None.title,
- state: 'opened', project_id: project.id)
- end
-
- benchmark_subject { finder.execute }
-
- it { is_expected.to iterate_per_second(2000) }
- end
- end
-end
diff --git a/spec/benchmarks/finders/trending_projects_finder_spec.rb b/spec/benchmarks/finders/trending_projects_finder_spec.rb
deleted file mode 100644
index 551ce21840d..00000000000
--- a/spec/benchmarks/finders/trending_projects_finder_spec.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-require 'spec_helper'
-
-describe TrendingProjectsFinder, benchmark: true do
- describe '#execute' do
- let(:finder) { described_class.new }
- let(:user) { create(:user) }
-
- # to_a is used to force actually running the query (instead of just building
- # it).
- benchmark_subject { finder.execute(user).non_archived.to_a }
-
- it { is_expected.to iterate_per_second(500) }
- end
-end
diff --git a/spec/benchmarks/lib/gitlab/markdown/reference_filter_spec.rb b/spec/benchmarks/lib/gitlab/markdown/reference_filter_spec.rb
deleted file mode 100644
index 3855763b200..00000000000
--- a/spec/benchmarks/lib/gitlab/markdown/reference_filter_spec.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-require 'spec_helper'
-
-describe Banzai::Filter::ReferenceFilter, benchmark: true do
- let(:input) do
- html = <<-EOF
-<p>Hello @alice and @bob, how are you doing today?</p>
-<p>This is simple @dummy text to see how the @ReferenceFilter class performs
-when @processing HTML.</p>
- EOF
-
- Nokogiri::HTML.fragment(html)
- end
-
- let(:project) { create(:empty_project) }
-
- let(:filter) { described_class.new(input, project: project) }
-
- describe '#replace_text_nodes_matching' do
- let(:iterations) { 6000 }
-
- describe 'with identical input and output HTML' do
- benchmark_subject do
- filter.replace_text_nodes_matching(User.reference_pattern) do |content|
- content
- end
- end
-
- it { is_expected.to iterate_per_second(iterations) }
- end
-
- describe 'with different input and output HTML' do
- benchmark_subject do
- filter.replace_text_nodes_matching(User.reference_pattern) do |content|
- '@eve'
- end
- end
-
- it { is_expected.to iterate_per_second(iterations) }
- end
- end
-end
diff --git a/spec/benchmarks/models/milestone_spec.rb b/spec/benchmarks/models/milestone_spec.rb
deleted file mode 100644
index a94afc4c40d..00000000000
--- a/spec/benchmarks/models/milestone_spec.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require 'spec_helper'
-
-describe Milestone, benchmark: true do
- describe '#sort_issues' do
- let(:milestone) { create(:milestone) }
-
- let(:issue1) { create(:issue, milestone: milestone) }
- let(:issue2) { create(:issue, milestone: milestone) }
- let(:issue3) { create(:issue, milestone: milestone) }
-
- let(:issue_ids) { [issue3.id, issue2.id, issue1.id] }
-
- benchmark_subject { milestone.sort_issues(issue_ids) }
-
- it { is_expected.to iterate_per_second(500) }
- end
-end
diff --git a/spec/benchmarks/models/project_spec.rb b/spec/benchmarks/models/project_spec.rb
deleted file mode 100644
index cee0949edc5..00000000000
--- a/spec/benchmarks/models/project_spec.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-require 'spec_helper'
-
-describe Project, benchmark: true do
- describe '.trending' do
- let(:group) { create(:group) }
- let(:project1) { create(:empty_project, :public, group: group) }
- let(:project2) { create(:empty_project, :public, group: group) }
-
- let(:iterations) { 500 }
-
- before do
- 2.times do
- create(:note_on_commit, project: project1)
- end
-
- create(:note_on_commit, project: project2)
- end
-
- describe 'without an explicit start date' do
- benchmark_subject { described_class.trending.to_a }
-
- it { is_expected.to iterate_per_second(iterations) }
- end
-
- describe 'with an explicit start date' do
- let(:date) { 1.month.ago }
-
- benchmark_subject { described_class.trending(date).to_a }
-
- it { is_expected.to iterate_per_second(iterations) }
- end
- end
-
- describe '.find_with_namespace' do
- let(:group) { create(:group, name: 'sisinmaru') }
- let(:project) { create(:project, name: 'maru', namespace: group) }
-
- describe 'using a capitalized namespace' do
- benchmark_subject { described_class.find_with_namespace('sisinmaru/MARU') }
-
- it { is_expected.to iterate_per_second(600) }
- end
-
- describe 'using a lowercased namespace' do
- benchmark_subject { described_class.find_with_namespace('sisinmaru/maru') }
-
- it { is_expected.to iterate_per_second(600) }
- end
- end
-end
diff --git a/spec/benchmarks/models/project_team_spec.rb b/spec/benchmarks/models/project_team_spec.rb
deleted file mode 100644
index 8b039ef7317..00000000000
--- a/spec/benchmarks/models/project_team_spec.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-require 'spec_helper'
-
-describe ProjectTeam, benchmark: true do
- describe '#max_member_access' do
- let(:group) { create(:group) }
- let(:project) { create(:empty_project, group: group) }
- let(:user) { create(:user) }
-
- before do
- project.team << [user, :master]
-
- 5.times do
- project.team << [create(:user), :reporter]
-
- project.group.add_user(create(:user), :reporter)
- end
- end
-
- benchmark_subject { project.team.max_member_access(user.id) }
-
- it { is_expected.to iterate_per_second(35000) }
- end
-end
diff --git a/spec/benchmarks/models/user_spec.rb b/spec/benchmarks/models/user_spec.rb
deleted file mode 100644
index 1be7a8d3ed9..00000000000
--- a/spec/benchmarks/models/user_spec.rb
+++ /dev/null
@@ -1,78 +0,0 @@
-require 'spec_helper'
-
-describe User, benchmark: true do
- describe '.all' do
- before do
- 10.times { create(:user) }
- end
-
- benchmark_subject { User.all.to_a }
-
- it { is_expected.to iterate_per_second(500) }
- end
-
- describe '.by_login' do
- before do
- %w{Alice Bob Eve}.each do |name|
- create(:user,
- email: "#{name}@gitlab.com",
- username: name,
- name: name)
- end
- end
-
- # The iteration count is based on the query taking little over 1 ms when
- # using PostgreSQL.
- let(:iterations) { 900 }
-
- describe 'using a capitalized username' do
- benchmark_subject { User.by_login('Alice') }
-
- it { is_expected.to iterate_per_second(iterations) }
- end
-
- describe 'using a lowercase username' do
- benchmark_subject { User.by_login('alice') }
-
- it { is_expected.to iterate_per_second(iterations) }
- end
-
- describe 'using a capitalized Email address' do
- benchmark_subject { User.by_login('Alice@gitlab.com') }
-
- it { is_expected.to iterate_per_second(iterations) }
- end
-
- describe 'using a lowercase Email address' do
- benchmark_subject { User.by_login('alice@gitlab.com') }
-
- it { is_expected.to iterate_per_second(iterations) }
- end
- end
-
- describe '.find_by_any_email' do
- let(:user) { create(:user) }
-
- describe 'using a user with only a single Email address' do
- let(:email) { user.email }
-
- benchmark_subject { User.find_by_any_email(email) }
-
- it { is_expected.to iterate_per_second(1000) }
- end
-
- describe 'using a user with multiple Email addresses' do
- let(:email) { user.emails.first.email }
-
- benchmark_subject { User.find_by_any_email(email) }
-
- before do
- 10.times do
- user.emails.create(email: FFaker::Internet.email)
- end
- end
-
- it { is_expected.to iterate_per_second(1000) }
- end
- end
-end
diff --git a/spec/benchmarks/services/projects/create_service_spec.rb b/spec/benchmarks/services/projects/create_service_spec.rb
deleted file mode 100644
index 25ed48c34fd..00000000000
--- a/spec/benchmarks/services/projects/create_service_spec.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-require 'spec_helper'
-
-describe Projects::CreateService, benchmark: true do
- describe '#execute' do
- let(:user) { create(:user, :admin) }
-
- let(:group) do
- group = create(:group)
-
- create(:group_member, group: group, user: user)
-
- group
- end
-
- benchmark_subject do
- name = SecureRandom.hex
- service = described_class.new(user,
- name: name,
- path: name,
- namespace_id: group.id,
- visibility_level: Gitlab::VisibilityLevel::PUBLIC)
-
- service.execute
- end
-
- it { is_expected.to iterate_per_second(0.5) }
- end
-end
diff --git a/spec/controllers/namespaces_controller_spec.rb b/spec/controllers/namespaces_controller_spec.rb
index d4a380cc2ee..77436958711 100644
--- a/spec/controllers/namespaces_controller_spec.rb
+++ b/spec/controllers/namespaces_controller_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe NamespacesController do
- let!(:user) { create(:user, :with_avatar) }
+ let!(:user) { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
describe "GET show" do
context "when the namespace belongs to a user" do
diff --git a/spec/controllers/profiles/avatars_controller_spec.rb b/spec/controllers/profiles/avatars_controller_spec.rb
index 85dff009bcf..ad5855df0a4 100644
--- a/spec/controllers/profiles/avatars_controller_spec.rb
+++ b/spec/controllers/profiles/avatars_controller_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Profiles::AvatarsController do
- let(:user) { create(:user, :with_avatar) }
+ let(:user) { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png")) }
before do
sign_in(user)
diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb
index 8e06d4bdc77..98ae424ed7c 100644
--- a/spec/controllers/projects/branches_controller_spec.rb
+++ b/spec/controllers/projects/branches_controller_spec.rb
@@ -17,49 +17,79 @@ describe Projects::BranchesController do
describe "POST create" do
render_views
- before do
- post :create,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- branch_name: branch,
- ref: ref
- end
+ context "on creation of a new branch" do
+ before do
+ post :create,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ branch_name: branch,
+ ref: ref
+ end
- context "valid branch name, valid source" do
- let(:branch) { "merge_branch" }
- let(:ref) { "master" }
- it 'redirects' do
- expect(subject).
- to redirect_to("/#{project.path_with_namespace}/tree/merge_branch")
+ context "valid branch name, valid source" do
+ let(:branch) { "merge_branch" }
+ let(:ref) { "master" }
+ it 'redirects' do
+ expect(subject).
+ to redirect_to("/#{project.path_with_namespace}/tree/merge_branch")
+ end
+ end
+
+ context "invalid branch name, valid ref" do
+ let(:branch) { "<script>alert('merge');</script>" }
+ let(:ref) { "master" }
+ it 'redirects' do
+ expect(subject).
+ to redirect_to("/#{project.path_with_namespace}/tree/alert('merge');")
+ end
+ end
+
+ context "valid branch name, invalid ref" do
+ let(:branch) { "merge_branch" }
+ let(:ref) { "<script>alert('ref');</script>" }
+ it { is_expected.to render_template('new') }
+ end
+
+ context "invalid branch name, invalid ref" do
+ let(:branch) { "<script>alert('merge');</script>" }
+ let(:ref) { "<script>alert('ref');</script>" }
+ it { is_expected.to render_template('new') }
+ end
+
+ context "valid branch name with encoded slashes" do
+ let(:branch) { "feature%2Ftest" }
+ let(:ref) { "<script>alert('ref');</script>" }
+ it { is_expected.to render_template('new') }
+ it { project.repository.branch_names.include?('feature/test') }
end
end
- context "invalid branch name, valid ref" do
- let(:branch) { "<script>alert('merge');</script>" }
- let(:ref) { "master" }
+ describe "created from the new branch button on issues" do
+ let(:branch) { "1-feature-branch" }
+ let!(:issue) { create(:issue, project: project) }
+
+
it 'redirects' do
+ post :create,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ branch_name: branch,
+ issue_iid: issue.iid
+
expect(subject).
- to redirect_to("/#{project.path_with_namespace}/tree/alert('merge');")
+ to redirect_to("/#{project.path_with_namespace}/tree/1-feature-branch")
end
- end
- context "valid branch name, invalid ref" do
- let(:branch) { "merge_branch" }
- let(:ref) { "<script>alert('ref');</script>" }
- it { is_expected.to render_template('new') }
- end
+ it 'posts a system note' do
+ expect(SystemNoteService).to receive(:new_issue_branch).with(issue, project, user, "1-feature-branch")
- context "invalid branch name, invalid ref" do
- let(:branch) { "<script>alert('merge');</script>" }
- let(:ref) { "<script>alert('ref');</script>" }
- it { is_expected.to render_template('new') }
- end
+ post :create,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ branch_name: branch,
+ issue_iid: issue.iid
+ end
- context "valid branch name with encoded slashes" do
- let(:branch) { "feature%2Ftest" }
- let(:ref) { "<script>alert('ref');</script>" }
- it { is_expected.to render_template('new') }
- it { project.repository.branch_names.include?('feature/test')}
end
end
diff --git a/spec/controllers/projects/forks_controller_spec.rb b/spec/controllers/projects/forks_controller_spec.rb
index 883bbaedd4e..70ed8f3a62e 100644
--- a/spec/controllers/projects/forks_controller_spec.rb
+++ b/spec/controllers/projects/forks_controller_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Projects::ForksController do
let(:user) { create(:user) }
- let(:project) { create(:project, visibility_level: Project::PUBLIC) }
+ let(:project) { create(:project, :public) }
let(:forked_project) { Projects::ForkService.new(project, user).execute }
let(:group) { create(:group, owner: forked_project.creator) }
diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb
index 0147bd2b953..2acbba469e3 100644
--- a/spec/controllers/projects/imports_controller_spec.rb
+++ b/spec/controllers/projects/imports_controller_spec.rb
@@ -19,7 +19,7 @@ describe Projects::ImportsController do
end
it 'sets flash.now if params is present' do
- get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, continue: { notice_now: 'Started' }
+ get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, continue: { to: '/', notice_now: 'Started' }
expect(flash.now[:notice]).to eq 'Started'
end
@@ -45,7 +45,7 @@ describe Projects::ImportsController do
end
it 'sets flash.now if params is present' do
- get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, continue: { notice_now: 'In progress' }
+ get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, continue: { to: '/', notice_now: 'In progress' }
expect(flash.now[:notice]).to eq 'In progress'
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 76d56bc989d..a49bd8960ee 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -1,16 +1,16 @@
require('spec_helper')
describe Projects::IssuesController do
- let(:project) { create(:project) }
- let(:user) { create(:user) }
- let(:issue) { create(:issue, project: project) }
+ describe "GET #index" do
+ let(:project) { create(:project_empty_repo) }
+ let(:user) { create(:user) }
+ let(:issue) { create(:issue, project: project) }
- before do
- sign_in(user)
- project.team << [user, :developer]
- end
+ before do
+ sign_in(user)
+ project.team << [user, :developer]
+ end
- describe "GET #index" do
it "returns index" do
get :index, namespace_id: project.namespace.path, project_id: project.path
@@ -38,6 +38,152 @@ describe Projects::IssuesController do
get :index, namespace_id: project.namespace.path, project_id: project.path
expect(response.status).to eq(404)
end
+ end
+
+ describe 'Confidential Issues' do
+ let(:project) { create(:project_empty_repo, :public) }
+ let(:assignee) { create(:assignee) }
+ let(:author) { create(:user) }
+ let(:non_member) { create(:user) }
+ let(:member) { create(:user) }
+ let(:admin) { create(:admin) }
+ let!(:issue) { create(:issue, project: project) }
+ let!(:unescaped_parameter_value) { create(:issue, :confidential, project: project, author: author) }
+ let!(:request_forgery_timing_attack) { create(:issue, :confidential, project: project, assignee: assignee) }
+
+ describe 'GET #index' do
+ it 'should not list confidential issues for guests' do
+ sign_out(:user)
+ get_issues
+
+ expect(assigns(:issues)).to eq [issue]
+ end
+
+ it 'should not list confidential issues for non project members' do
+ sign_in(non_member)
+ get_issues
+
+ expect(assigns(:issues)).to eq [issue]
+ end
+
+ it 'should list confidential issues for author' do
+ sign_in(author)
+ get_issues
+
+ expect(assigns(:issues)).to include unescaped_parameter_value
+ expect(assigns(:issues)).not_to include request_forgery_timing_attack
+ end
+
+ it 'should list confidential issues for assignee' do
+ sign_in(assignee)
+ get_issues
+
+ expect(assigns(:issues)).not_to include unescaped_parameter_value
+ expect(assigns(:issues)).to include request_forgery_timing_attack
+ end
+
+ it 'should list confidential issues for project members' do
+ sign_in(member)
+ project.team << [member, :developer]
+
+ get_issues
+
+ expect(assigns(:issues)).to include unescaped_parameter_value
+ expect(assigns(:issues)).to include request_forgery_timing_attack
+ end
+
+ it 'should list confidential issues for admin' do
+ sign_in(admin)
+ get_issues
+
+ expect(assigns(:issues)).to include unescaped_parameter_value
+ expect(assigns(:issues)).to include request_forgery_timing_attack
+ end
+
+ def get_issues
+ get :index,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param
+ end
+ end
+ shared_examples_for 'restricted action' do |http_status|
+ it 'returns 404 for guests' do
+ sign_out :user
+ go(id: unescaped_parameter_value.to_param)
+
+ expect(response).to have_http_status :not_found
+ end
+
+ it 'returns 404 for non project members' do
+ sign_in(non_member)
+ go(id: unescaped_parameter_value.to_param)
+
+ expect(response).to have_http_status :not_found
+ end
+
+ it "returns #{http_status[:success]} for author" do
+ sign_in(author)
+ go(id: unescaped_parameter_value.to_param)
+
+ expect(response).to have_http_status http_status[:success]
+ end
+
+ it "returns #{http_status[:success]} for assignee" do
+ sign_in(assignee)
+ go(id: request_forgery_timing_attack.to_param)
+
+ expect(response).to have_http_status http_status[:success]
+ end
+
+ it "returns #{http_status[:success]} for project members" do
+ sign_in(member)
+ project.team << [member, :developer]
+ go(id: unescaped_parameter_value.to_param)
+
+ expect(response).to have_http_status http_status[:success]
+ end
+
+ it "returns #{http_status[:success]} for admin" do
+ sign_in(admin)
+ go(id: unescaped_parameter_value.to_param)
+
+ expect(response).to have_http_status http_status[:success]
+ end
+ end
+
+ describe 'GET #show' do
+ it_behaves_like 'restricted action', success: 200
+
+ def go(id:)
+ get :show,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: id
+ end
+ end
+
+ describe 'GET #edit' do
+ it_behaves_like 'restricted action', success: 200
+
+ def go(id:)
+ get :edit,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: id
+ end
+ end
+
+ describe 'PUT #update' do
+ it_behaves_like 'restricted action', success: 302
+
+ def go(id:)
+ put :update,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: id,
+ issue: { title: 'New title' }
+ end
+ end
end
end
diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb
index 09ec4f18f9d..0ddbec9eac2 100644
--- a/spec/controllers/projects/repositories_controller_spec.rb
+++ b/spec/controllers/projects/repositories_controller_spec.rb
@@ -2,30 +2,41 @@ require "spec_helper"
describe Projects::RepositoriesController do
let(:project) { create(:project) }
- let(:user) { create(:user) }
describe "GET archive" do
- before do
- sign_in(user)
- project.team << [user, :developer]
- end
-
- it "uses Gitlab::Workhorse" do
- expect(Gitlab::Workhorse).to receive(:send_git_archive).with(project, "master", "zip")
+ context 'as a guest' do
+ it 'responds with redirect in correct format' do
+ get :archive, namespace_id: project.namespace.path, project_id: project.path, format: "zip"
- get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip"
+ expect(response.content_type).to start_with 'text/html'
+ expect(response).to be_redirect
+ end
end
- context "when the service raises an error" do
+ context 'as a user' do
+ let(:user) { create(:user) }
before do
- allow(Gitlab::Workhorse).to receive(:send_git_archive).and_raise("Archive failed")
+ project.team << [user, :developer]
+ sign_in(user)
end
+ it "uses Gitlab::Workhorse" do
+ expect(Gitlab::Workhorse).to receive(:send_git_archive).with(project, "master", "zip")
- it "renders Not Found" do
get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip"
+ end
+
+ context "when the service raises an error" do
+
+ before do
+ allow(Gitlab::Workhorse).to receive(:send_git_archive).and_raise("Archive failed")
+ end
+
+ it "renders Not Found" do
+ get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip"
- expect(response.status).to eq(404)
+ expect(response.status).to eq(404)
+ end
end
end
end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 6eee4dfe229..1893e946f5c 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -9,19 +9,6 @@ describe ProjectsController do
describe "GET show" do
- context "when requested by `go get`" do
- render_views
-
- it "renders the go-import meta tag" do
- get :show, "go-get" => "1", namespace_id: "bogus_namespace", id: "bogus_project"
-
- expect(response.body).to include("name='go-import'")
-
- content = "localhost/bogus_namespace/bogus_project git http://localhost/bogus_namespace/bogus_project.git"
- expect(response.body).to include("content='#{content}'")
- end
- end
-
context "rendering default project view" do
render_views
diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb
index 0d9f4b299bc..af5d043cf02 100644
--- a/spec/controllers/uploads_controller_spec.rb
+++ b/spec/controllers/uploads_controller_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe UploadsController do
- let!(:user) { create(:user, :with_avatar) }
+ let!(:user) { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
describe "GET show" do
context "when viewing a user avatar" do
diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb
index 722095de590..e72aa9479b7 100644
--- a/spec/factories/issues.rb
+++ b/spec/factories/issues.rb
@@ -4,6 +4,10 @@ FactoryGirl.define do
author
project
+ trait :confidential do
+ confidential true
+ end
+
trait :closed do
state :closed
end
diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb
index 6e70af10af3..ea2be8928d5 100644
--- a/spec/factories/labels.rb
+++ b/spec/factories/labels.rb
@@ -13,7 +13,7 @@
FactoryGirl.define do
factory :label do
- title "Bug"
+ sequence(:title) { |n| "label#{n}" }
color "#990000"
project
end
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index ca1c636fce4..e281e2f227b 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -51,11 +51,20 @@ FactoryGirl.define do
trait :with_diffs do
end
+ trait :without_diffs do
+ source_branch "improve/awesome"
+ target_branch "master"
+ end
+
trait :conflict do
source_branch "feature_conflict"
target_branch "feature"
end
+ trait :merged do
+ state :merged
+ end
+
trait :closed do
state :closed
end
@@ -84,6 +93,7 @@ FactoryGirl.define do
merge_user author
end
+ factory :merged_merge_request, traits: [:merged]
factory :closed_merge_request, traits: [:closed]
factory :reopened_merge_request, traits: [:reopened]
factory :merge_request_with_diffs, traits: [:with_diffs]
diff --git a/spec/factories/personal_snippets.rb b/spec/factories/personal_snippets.rb
index b493a6968ff..0f13b2c1020 100644
--- a/spec/factories/personal_snippets.rb
+++ b/spec/factories/personal_snippets.rb
@@ -1,15 +1,4 @@
FactoryGirl.define do
factory :personal_snippet, parent: :snippet, class: :PersonalSnippet do
- trait :public do
- visibility_level PersonalSnippet::PUBLIC
- end
-
- trait :internal do
- visibility_level PersonalSnippet::INTERNAL
- end
-
- trait :private do
- visibility_level PersonalSnippet::PRIVATE
- end
end
end
diff --git a/spec/factories/project_group_links.rb b/spec/factories/project_group_links.rb
new file mode 100644
index 00000000000..e73cc05f9d7
--- /dev/null
+++ b/spec/factories/project_group_links.rb
@@ -0,0 +1,6 @@
+FactoryGirl.define do
+ factory :project_group_link do
+ project
+ group
+ end
+end
diff --git a/spec/factories/project_snippets.rb b/spec/factories/project_snippets.rb
index 154442bd3db..d681a2c8483 100644
--- a/spec/factories/project_snippets.rb
+++ b/spec/factories/project_snippets.rb
@@ -1,9 +1,5 @@
FactoryGirl.define do
- factory :project_snippet do
+ factory :project_snippet, parent: :snippet, class: :ProjectSnippet do
project
- author
- title
- content
- file_name
end
end
diff --git a/spec/factories/snippets.rb b/spec/factories/snippets.rb
index b9127b3d75e..365f12a0c95 100644
--- a/spec/factories/snippets.rb
+++ b/spec/factories/snippets.rb
@@ -12,5 +12,17 @@ FactoryGirl.define do
title
content
file_name
+
+ trait :public do
+ visibility_level Snippet::PUBLIC
+ end
+
+ trait :internal do
+ visibility_level Snippet::INTERNAL
+ end
+
+ trait :private do
+ visibility_level Snippet::PRIVATE
+ end
end
end
diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb
index bd85b1d798a..7ae06c27840 100644
--- a/spec/factories/todos.rb
+++ b/spec/factories/todos.rb
@@ -5,14 +5,15 @@
# id :integer not null, primary key
# user_id :integer not null
# project_id :integer not null
-# target_id :integer not null
+# target_id :integer
# target_type :string not null
# author_id :integer
-# note_id :integer
# action :integer not null
# state :string not null
# created_at :datetime
# updated_at :datetime
+# note_id :integer
+# commit_id :string
#
FactoryGirl.define do
@@ -30,5 +31,10 @@ FactoryGirl.define do
trait :mentioned do
action { Todo::MENTIONED }
end
+
+ trait :on_commit do
+ commit_id RepoHelpers.sample_commit.id
+ target_type "Commit"
+ end
end
end
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
index 785c2a3d811..a5c60c51c5b 100644
--- a/spec/factories/users.rb
+++ b/spec/factories/users.rb
@@ -23,13 +23,6 @@ FactoryGirl.define do
end
end
- trait :with_avatar do
- avatar { fixture_file_upload(Rails.root.join(*%w(spec fixtures dk.png)), 'image/png') }
- avatar_crop_x 0
- avatar_crop_y 0
- avatar_crop_size 256
- end
-
factory :omniauth_user do
transient do
extern_uid '123456'
diff --git a/spec/features/issues/filter_by_milestone_spec.rb b/spec/features/issues/filter_by_milestone_spec.rb
index 591866b40d4..f6e33f651c4 100644
--- a/spec/features/issues/filter_by_milestone_spec.rb
+++ b/spec/features/issues/filter_by_milestone_spec.rb
@@ -1,8 +1,6 @@
require 'rails_helper'
feature 'Issue filtering by Milestone', feature: true do
- include Select2Helper
-
let(:project) { create(:project, :public) }
let(:milestone) { create(:milestone, project: project) }
@@ -31,6 +29,9 @@ feature 'Issue filtering by Milestone', feature: true do
end
def filter_by_milestone(title)
- select2(title, from: '#milestone_title')
+ find(".js-milestone-select").click
+ sleep 0.5
+ find(".milestone-filter a", text: title).click
+ sleep 1
end
end
diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb
new file mode 100644
index 00000000000..6fda0c31866
--- /dev/null
+++ b/spec/features/issues/move_spec.rb
@@ -0,0 +1,87 @@
+require 'rails_helper'
+
+feature 'issue move to another project' do
+ let(:user) { create(:user) }
+ let(:old_project) { create(:project) }
+ let(:text) { 'Some issue description' }
+
+ let(:issue) do
+ create(:issue, description: text, project: old_project, author: user)
+ end
+
+ background { login_as(user) }
+
+ context 'user does not have permission to move issue' do
+ background do
+ old_project.team << [user, :guest]
+
+ edit_issue(issue)
+ end
+
+ scenario 'moving issue to another project not allowed' do
+ expect(page).to have_no_select('move_to_project_id')
+ end
+ end
+
+ context 'user has permission to move issue' do
+ let!(:mr) { create(:merge_request, source_project: old_project) }
+ let(:new_project) { create(:project) }
+ let(:text) { 'Text with !1' }
+ let(:cross_reference) { old_project.to_reference }
+
+ background do
+ old_project.team << [user, :reporter]
+ new_project.team << [user, :reporter]
+
+ edit_issue(issue)
+ end
+
+ scenario 'moving issue to another project' do
+ select(new_project.name_with_namespace, from: 'move_to_project_id')
+ click_button('Save changes')
+
+ expect(current_url).to include project_path(new_project)
+
+ page.within('.issue') do
+ expect(page).to have_content("Text with #{cross_reference}!1")
+ expect(page).to have_content("Moved from #{cross_reference}#1")
+ expect(page).to have_content(issue.title)
+ end
+ end
+
+ context 'projects user does not have permission to move issue to exist' do
+ let!(:private_project) { create(:project, :private) }
+ let(:another_project) { create(:project) }
+ background { another_project.team << [user, :guest] }
+
+ scenario 'browsing projects in projects select' do
+ options = [ '', 'No project', new_project.name_with_namespace ]
+ expect(page).to have_select('move_to_project_id', options: options)
+ end
+ end
+
+ context 'issue has been already moved' do
+ let(:new_issue) { create(:issue, project: new_project) }
+ let(:issue) do
+ create(:issue, project: old_project, author: user, moved_to: new_issue)
+ end
+
+ scenario 'user wants to move issue that has already been moved' do
+ expect(page).to have_no_select('move_to_project_id')
+ end
+ end
+ end
+
+ def edit_issue(issue)
+ visit issue_path(issue)
+ page.within('.issuable-header') { click_link 'Edit' }
+ end
+
+ def issue_path(issue)
+ namespace_project_issue_path(issue.project.namespace, issue.project, issue)
+ end
+
+ def project_path(project)
+ namespace_project_path(new_project.namespace, new_project)
+ end
+end
diff --git a/spec/features/issues/new_branch_button_spec.rb b/spec/features/issues/new_branch_button_spec.rb
new file mode 100644
index 00000000000..9219b767547
--- /dev/null
+++ b/spec/features/issues/new_branch_button_spec.rb
@@ -0,0 +1,49 @@
+require 'rails_helper'
+
+feature 'Start new branch from an issue', feature: true do
+ let!(:project) { create(:project) }
+ let!(:issue) { create(:issue, project: project) }
+ let!(:user) { create(:user)}
+
+ context "for team members" do
+ before do
+ project.team << [user, :master]
+ login_as(user)
+ end
+
+ it 'shown the new branch button', js: false do
+ visit namespace_project_issue_path(project.namespace, project, issue)
+
+ expect(page).to have_link "New Branch"
+ end
+
+ context "when there is a referenced merge request" do
+ let(:note) do
+ create(:note, :on_issue, :system, project: project,
+ note: "mentioned in !#{referenced_mr.iid}")
+ end
+ let(:referenced_mr) do
+ create(:merge_request, :simple, source_project: project, target_project: project,
+ description: "Fixes ##{issue.iid}", author: user)
+ end
+
+ before do
+ issue.notes << note
+
+ visit namespace_project_issue_path(project.namespace, project, issue)
+ end
+
+ it "hides the new branch button", js: true do
+ expect(page).not_to have_link "New Branch"
+ expect(page).to have_content /1 Related Merge Request/
+ end
+ end
+ end
+
+ context "for visiters" do
+ it 'no button is shown', js: false do
+ visit namespace_project_issue_path(project.namespace, project, issue)
+ expect(page).not_to have_link "New Branch"
+ end
+ end
+end
diff --git a/spec/features/merge_requests/filter_by_milestone_spec.rb b/spec/features/merge_requests/filter_by_milestone_spec.rb
index f70214e1122..b76e4c74c79 100644
--- a/spec/features/merge_requests/filter_by_milestone_spec.rb
+++ b/spec/features/merge_requests/filter_by_milestone_spec.rb
@@ -1,8 +1,6 @@
require 'rails_helper'
feature 'Merge Request filtering by Milestone', feature: true do
- include Select2Helper
-
let(:project) { create(:project, :public) }
let(:milestone) { create(:milestone, project: project) }
@@ -31,6 +29,7 @@ feature 'Merge Request filtering by Milestone', feature: true do
end
def filter_by_milestone(title)
- select2(title, from: '#milestone_title')
+ find(".js-milestone-select").click
+ find(".milestone-filter a", text: title).click
end
end
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
index 1a360cd1ebc..d9a8058efd9 100644
--- a/spec/features/notes_on_merge_requests_spec.rb
+++ b/spec/features/notes_on_merge_requests_spec.rb
@@ -22,7 +22,7 @@ describe 'Comments', feature: true do
it 'should be valid' do
is_expected.to have_css('.js-main-target-form', visible: true, count: 1)
expect(find('.js-main-target-form input[type=submit]').value).
- to eq('Add Comment')
+ to eq('Comment')
page.within('.js-main-target-form') do
expect(page).not_to have_link('Cancel')
end
@@ -49,7 +49,7 @@ describe 'Comments', feature: true do
page.within('.js-main-target-form') do
fill_in 'note[note]', with: 'This is awsome!'
find('.js-md-preview-button').click
- click_button 'Add Comment'
+ click_button 'Comment'
end
end
@@ -202,7 +202,7 @@ describe 'Comments', feature: true do
before do
page.within("tr[id='#{line_code_2}'] + .js-temp-notes-holder") do
fill_in 'note[note]', with: 'Another comment on line 10'
- click_button('Add Comment')
+ click_button('Comment')
end
end
diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb
index 57563add74c..f88c591d897 100644
--- a/spec/features/security/project/internal_access_spec.rb
+++ b/spec/features/security/project/internal_access_spec.rb
@@ -8,10 +8,12 @@ describe "Internal Project Access", feature: true do
let(:master) { create(:user) }
let(:guest) { create(:user) }
let(:reporter) { create(:user) }
+ let(:external_team_member) { create(:user, external: true) }
before do
# full access
project.team << [master, :master]
+ project.team << [external_team_member, :master]
# readonly
project.team << [reporter, :reporter]
@@ -34,6 +36,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -45,6 +49,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -56,6 +62,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -67,6 +75,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -78,6 +88,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -89,22 +101,23 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/blob" do
- before do
- commit = project.repository.commit
- path = '.gitignore'
- @blob_path = namespace_project_blob_path(project.namespace, project, File.join(commit.id, path))
- end
+ let(:commit) { project.repository.commit }
+ subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore')) }
- it { expect(@blob_path).to be_allowed_for master }
- it { expect(@blob_path).to be_allowed_for reporter }
- it { expect(@blob_path).to be_allowed_for :admin }
- it { expect(@blob_path).to be_allowed_for guest }
- it { expect(@blob_path).to be_allowed_for :user }
- it { expect(@blob_path).to be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/edit" do
@@ -115,6 +128,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -126,6 +141,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -137,6 +154,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -149,6 +168,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -160,6 +181,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -171,6 +194,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -182,6 +207,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -193,6 +220,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -209,6 +238,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -225,6 +256,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -236,6 +269,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
end
diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb
index a1e111c6cab..19f287ce7a4 100644
--- a/spec/features/security/project/private_access_spec.rb
+++ b/spec/features/security/project/private_access_spec.rb
@@ -8,10 +8,12 @@ describe "Private Project Access", feature: true do
let(:master) { create(:user) }
let(:guest) { create(:user) }
let(:reporter) { create(:user) }
+ let(:external_team_member) { create(:user, external: true) }
before do
# full access
project.team << [master, :master]
+ project.team << [external_team_member, :master]
# readonly
project.team << [reporter, :reporter]
@@ -34,6 +36,8 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -45,6 +49,8 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -56,6 +62,8 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -67,6 +75,7 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -78,6 +87,8 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -89,22 +100,23 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/blob" do
- before do
- commit = project.repository.commit
- path = '.gitignore'
- @blob_path = namespace_project_blob_path(project.namespace, project, File.join(commit.id, path))
- end
+ let(:commit) { project.repository.commit }
+ subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore'))}
- it { expect(@blob_path).to be_allowed_for master }
- it { expect(@blob_path).to be_allowed_for reporter }
- it { expect(@blob_path).to be_allowed_for :admin }
- it { expect(@blob_path).to be_denied_for guest }
- it { expect(@blob_path).to be_denied_for :user }
- it { expect(@blob_path).to be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/edit" do
@@ -115,6 +127,8 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -126,6 +140,8 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -137,6 +153,8 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -149,6 +167,8 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -160,6 +180,8 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -171,6 +193,8 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -187,6 +211,8 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -203,6 +229,8 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -214,6 +242,8 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
end
diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
index b98476f854e..4e135076367 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -38,6 +38,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
it { is_expected.to be_allowed_for :visitor }
end
@@ -49,6 +50,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
it { is_expected.to be_allowed_for :visitor }
end
@@ -60,6 +62,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
it { is_expected.to be_allowed_for :visitor }
end
@@ -71,6 +74,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
it { is_expected.to be_allowed_for :visitor }
end
@@ -82,6 +86,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
it { is_expected.to be_allowed_for :visitor }
end
@@ -93,6 +98,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor }
end
@@ -107,6 +113,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
it { is_expected.to be_allowed_for :visitor }
end
@@ -118,6 +125,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor }
end
end
@@ -135,6 +143,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
it { is_expected.to be_allowed_for :visitor }
end
@@ -146,23 +155,22 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor }
end
end
describe "GET /:project_path/blob" do
- before do
- commit = project.repository.commit
- path = '.gitignore'
- @blob_path = namespace_project_blob_path(project.namespace, project, File.join(commit.id, path))
- end
+ let(:commit) { project.repository.commit }
+
+ subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore')) }
- it { expect(@blob_path).to be_allowed_for master }
- it { expect(@blob_path).to be_allowed_for reporter }
- it { expect(@blob_path).to be_allowed_for :admin }
- it { expect(@blob_path).to be_allowed_for guest }
- it { expect(@blob_path).to be_allowed_for :user }
- it { expect(@blob_path).to be_allowed_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
end
describe "GET /:project_path/edit" do
@@ -173,6 +181,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor }
end
@@ -184,6 +193,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor }
end
@@ -195,6 +205,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
it { is_expected.to be_allowed_for :visitor }
end
@@ -207,6 +218,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor }
end
@@ -218,6 +230,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
it { is_expected.to be_allowed_for :visitor }
end
@@ -229,6 +242,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor }
end
@@ -240,6 +254,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
it { is_expected.to be_allowed_for :visitor }
end
@@ -251,6 +266,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor }
end
@@ -267,6 +283,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
it { is_expected.to be_allowed_for :visitor }
end
@@ -283,6 +300,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
it { is_expected.to be_allowed_for :visitor }
end
@@ -294,6 +312,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor }
end
end
diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb
index f32641ef0f6..fae0da9d898 100644
--- a/spec/finders/projects_finder_spec.rb
+++ b/spec/finders/projects_finder_spec.rb
@@ -17,6 +17,10 @@ describe ProjectsFinder do
create(:project, :public, group: group, name: 'C', path: 'C')
end
+ let!(:shared_project) do
+ create(:project, :private, name: 'D', path: 'D')
+ end
+
let(:finder) { described_class.new }
describe 'without a group' do
@@ -56,7 +60,35 @@ describe ProjectsFinder do
describe 'with a user' do
subject { finder.execute(user, group: group) }
- it { is_expected.to eq([public_project, internal_project]) }
+ describe 'without shared projects' do
+ it { is_expected.to eq([public_project, internal_project]) }
+ end
+
+ describe 'with shared projects and group membership' do
+ before do
+ group.add_user(user, Gitlab::Access::DEVELOPER)
+
+ shared_project.project_group_links.
+ create(group_access: Gitlab::Access::MASTER, group: group)
+ end
+
+ it do
+ is_expected.to eq([shared_project, public_project, internal_project])
+ end
+ end
+
+ describe 'with shared projects and project membership' do
+ before do
+ shared_project.team.add_user(user, Gitlab::Access::DEVELOPER)
+
+ shared_project.project_group_links.
+ create(group_access: Gitlab::Access::MASTER, group: group)
+ end
+
+ it do
+ is_expected.to eq([shared_project, public_project, internal_project])
+ end
+ end
end
end
end
diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb
index 1b4ffc2d717..7fdc5e5d7aa 100644
--- a/spec/finders/snippets_finder_spec.rb
+++ b/spec/finders/snippets_finder_spec.rb
@@ -5,15 +5,14 @@ describe SnippetsFinder do
let(:user1) { create :user }
let(:group) { create :group }
- let(:project1) { create(:empty_project, :public, group: group) }
- let(:project2) { create(:empty_project, :private, group: group) }
-
+ let(:project1) { create(:empty_project, :public, group: group) }
+ let(:project2) { create(:empty_project, :private, group: group) }
context ':all filter' do
before do
- @snippet1 = create(:personal_snippet, visibility_level: Snippet::PRIVATE)
- @snippet2 = create(:personal_snippet, visibility_level: Snippet::INTERNAL)
- @snippet3 = create(:personal_snippet, visibility_level: Snippet::PUBLIC)
+ @snippet1 = create(:personal_snippet, :private)
+ @snippet2 = create(:personal_snippet, :internal)
+ @snippet3 = create(:personal_snippet, :public)
end
it "returns all private and internal snippets" do
@@ -31,9 +30,9 @@ describe SnippetsFinder do
context ':by_user filter' do
before do
- @snippet1 = create(:personal_snippet, visibility_level: Snippet::PRIVATE, author: user)
- @snippet2 = create(:personal_snippet, visibility_level: Snippet::INTERNAL, author: user)
- @snippet3 = create(:personal_snippet, visibility_level: Snippet::PUBLIC, author: user)
+ @snippet1 = create(:personal_snippet, :private, author: user)
+ @snippet2 = create(:personal_snippet, :internal, author: user)
+ @snippet3 = create(:personal_snippet, :public, author: user)
end
it "returns all public and internal snippets" do
@@ -75,9 +74,9 @@ describe SnippetsFinder do
context 'by_project filter' do
before do
- @snippet1 = create(:project_snippet, visibility_level: Snippet::PRIVATE, project: project1)
- @snippet2 = create(:project_snippet, visibility_level: Snippet::INTERNAL, project: project1)
- @snippet3 = create(:project_snippet, visibility_level: Snippet::PUBLIC, project: project1)
+ @snippet1 = create(:project_snippet, :private, project: project1)
+ @snippet2 = create(:project_snippet, :internal, project: project1)
+ @snippet3 = create(:project_snippet, :public, project: project1)
end
it "returns public snippets for unauthorized user" do
@@ -93,7 +92,7 @@ describe SnippetsFinder do
end
it "returns all snippets for project members" do
- project1.team << [user, :developer]
+ project1.team << [user, :developer]
snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1)
expect(snippets).to include(@snippet1, @snippet2, @snippet3)
end
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 8013b31524f..f6c1005d265 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -77,7 +77,7 @@ describe ApplicationHelper do
let(:avatar_file_path) { File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') }
it 'should return an url for the avatar' do
- user = create(:user, :with_avatar, avatar: File.open(avatar_file_path))
+ user = create(:user, avatar: File.open(avatar_file_path))
expect(helper.avatar_icon(user.email).to_s).
to match("/uploads/user/avatar/#{user.id}/banana_sample.gif")
@@ -88,7 +88,7 @@ describe ApplicationHelper do
# Must be stubbed after the stub above, and separately
stub_config_setting(url: Settings.send(:build_gitlab_url))
- user = create(:user, :with_avatar, avatar: File.open(avatar_file_path))
+ user = create(:user, avatar: File.open(avatar_file_path))
expect(helper.avatar_icon(user.email).to_s).
to match("/gitlab/uploads/user/avatar/#{user.id}/banana_sample.gif")
@@ -102,7 +102,7 @@ describe ApplicationHelper do
describe 'using a User' do
it 'should return an URL for the avatar' do
- user = create(:user, :with_avatar, avatar: File.open(avatar_file_path))
+ user = create(:user, avatar: File.open(avatar_file_path))
expect(helper.avatar_icon(user).to_s).
to match("/uploads/user/avatar/#{user.id}/banana_sample.gif")
diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index 4f129eca183..eca8bc8ab2d 100644
--- a/spec/helpers/labels_helper_spec.rb
+++ b/spec/helpers/labels_helper_spec.rb
@@ -11,7 +11,7 @@ describe LabelsHelper do
end
it 'uses the instance variable' do
- expect(link_to_label(label)).to match %r{<a href="/#{@project.to_reference}/issues\?label_name=#{label.name}">.*</a>}
+ expect(link_to_label(label)).to match %r{<a href="/#{@project.to_reference}/issues\?label_name=#{label.name}"><span class="[\w\s\-]*has_tooltip".*</span></a>}
end
end
@@ -39,6 +39,14 @@ describe LabelsHelper do
end
end
+ context 'with a tooltip argument' do
+ context 'set to false' do
+ it 'does not include the has_tooltip class' do
+ expect(link_to_label(label, tooltip: false)).not_to match %r{has_tooltip}
+ end
+ end
+ end
+
context 'with block' do
it 'passes the block to link_to' do
link = link_to_label(label) { 'Foo' }
@@ -49,7 +57,7 @@ describe LabelsHelper do
context 'without block' do
it 'uses render_colored_label as the link content' do
expect(self).to receive(:render_colored_label).
- with(label).and_return('Foo')
+ with(label, tooltip: true).and_return('Foo')
expect(link_to_label(label)).to match('Foo')
end
end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 53207767581..86cbd29830c 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -94,4 +94,23 @@ describe ProjectsHelper do
end
end
end
+
+ describe 'default_clone_protocol' do
+ describe 'using HTTP' do
+ it 'returns HTTP' do
+ expect(helper).to receive(:current_user).and_return(nil)
+
+ expect(helper.send(:default_clone_protocol)).to eq('http')
+ end
+ end
+
+ describe 'using HTTPS' do
+ it 'returns HTTPS' do
+ allow(Gitlab.config.gitlab).to receive(:protocol).and_return('https')
+ expect(helper).to receive(:current_user).and_return(nil)
+
+ expect(helper.send(:default_clone_protocol)).to eq('https')
+ end
+ end
+ end
end
diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb
index aafc24397a9..cd7596a763d 100644
--- a/spec/helpers/visibility_level_helper_spec.rb
+++ b/spec/helpers/visibility_level_helper_spec.rb
@@ -58,7 +58,7 @@ describe VisibilityLevelHelper do
describe "skip_level?" do
describe "forks" do
- let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
+ let(:project) { create(:project, :internal) }
let(:fork_project) { create(:forked_project_with_submodules) }
before do
@@ -74,7 +74,7 @@ describe VisibilityLevelHelper do
end
describe "non-forked project" do
- let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
+ let(:project) { create(:project, :internal) }
it "skips levels" do
expect(skip_level?(project, Gitlab::VisibilityLevel::PUBLIC)).to be_falsey
@@ -84,7 +84,7 @@ describe VisibilityLevelHelper do
end
describe "Snippet" do
- let(:snippet) { create(:snippet, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
+ let(:snippet) { create(:snippet, :internal) }
it "skips levels" do
expect(skip_level?(snippet, Gitlab::VisibilityLevel::PUBLIC)).to be_falsey
diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb
index e2d21f53b7e..4c1d4a2d24c 100644
--- a/spec/lib/banzai/filter/label_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb
@@ -56,7 +56,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
describe 'label span element' do
it 'includes default classes' do
doc = reference_filter("Label #{reference}")
- expect(doc.css('a span').first.attr('class')).to eq 'label color-label'
+ expect(doc.css('a span').first.attr('class')).to eq 'label color-label has_tooltip'
end
it 'includes a style attribute' do
diff --git a/spec/lib/banzai/filter/redactor_filter_spec.rb b/spec/lib/banzai/filter/redactor_filter_spec.rb
index e9bb388e361..9acf6304bcb 100644
--- a/spec/lib/banzai/filter/redactor_filter_spec.rb
+++ b/spec/lib/banzai/filter/redactor_filter_spec.rb
@@ -44,8 +44,78 @@ describe Banzai::Filter::RedactorFilter, lib: true do
end
end
- context "for user references" do
+ context 'with data-issue' do
+ context 'for confidential issues' do
+ it 'removes references for non project members' do
+ non_member = create(:user)
+ project = create(:empty_project, :public)
+ issue = create(:issue, :confidential, project: project)
+
+ link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
+ doc = filter(link, current_user: non_member)
+
+ expect(doc.css('a').length).to eq 0
+ end
+
+ it 'allows references for author' do
+ author = create(:user)
+ project = create(:empty_project, :public)
+ issue = create(:issue, :confidential, project: project, author: author)
+
+ link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
+ doc = filter(link, current_user: author)
+
+ expect(doc.css('a').length).to eq 1
+ end
+
+ it 'allows references for assignee' do
+ assignee = create(:user)
+ project = create(:empty_project, :public)
+ issue = create(:issue, :confidential, project: project, assignee: assignee)
+
+ link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
+ doc = filter(link, current_user: assignee)
+ expect(doc.css('a').length).to eq 1
+ end
+
+ it 'allows references for project members' do
+ member = create(:user)
+ project = create(:empty_project, :public)
+ project.team << [member, :developer]
+ issue = create(:issue, :confidential, project: project)
+
+ link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
+ doc = filter(link, current_user: member)
+
+ expect(doc.css('a').length).to eq 1
+ end
+
+ it 'allows references for admin' do
+ admin = create(:admin)
+ project = create(:empty_project, :public)
+ issue = create(:issue, :confidential, project: project)
+
+ link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
+ doc = filter(link, current_user: admin)
+
+ expect(doc.css('a').length).to eq 1
+ end
+ end
+
+ it 'allows references for non confidential issues' do
+ user = create(:user)
+ project = create(:empty_project, :public)
+ issue = create(:issue, project: project)
+
+ link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
+ doc = filter(link, current_user: user)
+
+ expect(doc.css('a').length).to eq 1
+ end
+ end
+
+ context "for user references" do
context 'with data-group' do
it 'removes unpermitted Group references' do
user = create(:user)
diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb
index 4a7b00c7660..27ce312b11c 100644
--- a/spec/lib/banzai/filter/sanitization_filter_spec.rb
+++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb
@@ -149,10 +149,20 @@ describe Banzai::Filter::SanitizationFilter, lib: true do
output: '<a href="java"></a>'
},
+ 'protocol-based JS injection: invalid URL char' => {
+ input: '<img src=java\script:alert("XSS")>',
+ output: '<img>'
+ },
+
'protocol-based JS injection: spaces and entities' => {
input: '<a href=" &#14; javascript:alert(\'XSS\');">foo</a>',
output: '<a href="">foo</a>'
},
+
+ 'protocol whitespace' => {
+ input: '<a href=" http://example.com/"></a>',
+ output: '<a href="http://example.com/"></a>'
+ }
}
protocols.each do |name, data|
@@ -177,6 +187,16 @@ describe Banzai::Filter::SanitizationFilter, lib: true do
expect(output.to_html).to eq '<a>XSS</a>'
end
+ it 'disallows invalid URIs' do
+ expect(Addressable::URI).to receive(:parse).with('foo://example.com').
+ and_raise(Addressable::URI::InvalidURIError)
+
+ input = '<a href="foo://example.com">Foo</a>'
+ output = filter(input)
+
+ expect(output.to_html).to eq '<a>Foo</a>'
+ end
+
it 'allows non-standard anchor schemes' do
exp = %q{<a href="irc://irc.freenode.net/git">IRC</a>}
act = filter(exp)
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index f3394910c5b..fab6412d29f 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -397,7 +397,7 @@ module Ci
services: ["mysql"],
before_script: ["pwd"],
rspec: {
- artifacts: { paths: ["logs/", "binaries/"], untracked: true },
+ artifacts: { paths: ["logs/", "binaries/"], untracked: true, name: "custom_name" },
script: "rspec"
}
})
@@ -417,6 +417,7 @@ module Ci
image: "ruby:2.1",
services: ["mysql"],
artifacts: {
+ name: "custom_name",
paths: ["logs/", "binaries/"],
untracked: true
}
@@ -427,6 +428,112 @@ module Ci
end
end
+ describe "Dependencies" do
+ let(:config) do
+ {
+ build1: { stage: 'build', script: 'test' },
+ build2: { stage: 'build', script: 'test' },
+ test1: { stage: 'test', script: 'test', dependencies: dependencies },
+ test2: { stage: 'test', script: 'test' },
+ deploy: { stage: 'test', script: 'test' }
+ }
+ end
+
+ subject { GitlabCiYamlProcessor.new(YAML.dump(config)) }
+
+ context 'no dependencies' do
+ let(:dependencies) { }
+
+ it { expect { subject }.to_not raise_error }
+ end
+
+ context 'dependencies to builds' do
+ let(:dependencies) { [:build1, :build2] }
+
+ it { expect { subject }.to_not raise_error }
+ end
+
+ context 'undefined dependency' do
+ let(:dependencies) { [:undefined] }
+
+ it { expect { subject }.to raise_error(GitlabCiYamlProcessor::ValidationError, 'test1 job: undefined dependency: undefined') }
+ end
+
+ context 'dependencies to deploy' do
+ let(:dependencies) { [:deploy] }
+
+ it { expect { subject }.to raise_error(GitlabCiYamlProcessor::ValidationError, 'test1 job: dependency deploy is not defined in prior stages') }
+ end
+ end
+
+ describe "Hidden jobs" do
+ let(:config) do
+ YAML.dump({
+ '.hidden_job' => { script: 'test' },
+ 'normal_job' => { script: 'test' }
+ })
+ end
+
+ let(:config_processor) { GitlabCiYamlProcessor.new(config) }
+
+ subject { config_processor.builds_for_stage_and_ref("test", "master") }
+
+ it "doesn't create jobs that starts with dot" do
+ expect(subject.size).to eq(1)
+ expect(subject.first).to eq({
+ except: nil,
+ stage: "test",
+ stage_idx: 1,
+ name: :normal_job,
+ only: nil,
+ commands: "\ntest",
+ tag_list: [],
+ options: {},
+ when: "on_success",
+ allow_failure: false
+ })
+ end
+ end
+
+ describe "YAML Alias/Anchor" do
+ it "is correctly supported for jobs" do
+ config = <<EOT
+job1: &JOBTMPL
+ script: execute-script-for-job
+
+job2: *JOBTMPL
+EOT
+
+ config_processor = GitlabCiYamlProcessor.new(config)
+
+ expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(2)
+ expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
+ except: nil,
+ stage: "test",
+ stage_idx: 1,
+ name: :job1,
+ only: nil,
+ commands: "\nexecute-script-for-job",
+ tag_list: [],
+ options: {},
+ when: "on_success",
+ allow_failure: false
+ })
+ expect(config_processor.builds_for_stage_and_ref("test", "master").second).to eq({
+ except: nil,
+ stage: "test",
+ stage_idx: 1,
+ name: :job2,
+ only: nil,
+ commands: "\nexecute-script-for-job",
+ tag_list: [],
+ options: {},
+ when: "on_success",
+ allow_failure: false
+ })
+ end
+ end
+
describe "Error handling" do
it "fails to parse YAML" do
expect{GitlabCiYamlProcessor.new("invalid: yaml: test")}.to raise_error(Psych::SyntaxError)
@@ -590,6 +697,13 @@ module Ci
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: when parameter should be on_success, on_failure or always")
end
+ it "returns errors if job artifacts:name is not an a string" do
+ config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { name: 1 } } })
+ expect do
+ GitlabCiYamlProcessor.new(config)
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:name parameter should be a string")
+ end
+
it "returns errors if job artifacts:untracked is not an array of strings" do
config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { untracked: "string" } } })
expect do
@@ -645,6 +759,13 @@ module Ci
GitlabCiYamlProcessor.new(config)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: cache:paths parameter should be an array of strings")
end
+
+ it "returns errors if job dependencies is not an array of strings" do
+ config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", dependencies: "string" } })
+ expect do
+ GitlabCiYamlProcessor.new(config)
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: dependencies parameter should be an array of strings")
+ end
end
end
end
diff --git a/spec/lib/ci/status_spec.rb b/spec/lib/ci/status_spec.rb
index 1539720bb8d..47f3df6e3ce 100644
--- a/spec/lib/ci/status_spec.rb
+++ b/spec/lib/ci/status_spec.rb
@@ -48,6 +48,29 @@ describe Ci::Status do
it { is_expected.to eq 'success' }
end
+ context 'success and canceled' do
+ let(:statuses) do
+ [create(type, status: :success), create(type, status: :canceled)]
+ end
+ it { is_expected.to eq 'failed' }
+ end
+
+ context 'all canceled' do
+ let(:statuses) do
+ [create(type, status: :canceled), create(type, status: :canceled)]
+ end
+ it { is_expected.to eq 'canceled' }
+ end
+
+ context 'success and canceled but allowed to fail' do
+ let(:statuses) do
+ [create(type, status: :success),
+ create(type, status: :canceled, allow_failure: true)]
+ end
+
+ it { is_expected.to eq 'success' }
+ end
+
context 'one finished and second running but allowed to fail' do
let(:statuses) do
[create(type, status: :success),
diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
new file mode 100644
index 00000000000..c413132abe5
--- /dev/null
+++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
@@ -0,0 +1,88 @@
+require 'spec_helper'
+
+describe Gitlab::BitbucketImport::Importer, lib: true do
+ before do
+ Gitlab.config.omniauth.providers << OpenStruct.new(app_id: "asd123", app_secret: "asd123", name: "bitbucket")
+ end
+
+ let(:statuses) do
+ [
+ "open",
+ "resolved",
+ "on hold",
+ "invalid",
+ "duplicate",
+ "wontfix",
+ "closed" # undocumented status
+ ]
+ end
+ let(:sample_issues_statuses) do
+ issues = []
+
+ statuses.map.with_index do |status, index|
+ issues << {
+ local_id: index,
+ status: status,
+ title: "Issue #{index}",
+ content: "Some content to issue #{index}"
+ }
+ end
+
+ issues
+ end
+
+ let(:project_identifier) { 'namespace/repo' }
+ let(:data) do
+ {
+ bb_session: {
+ bitbucket_access_token: "123456",
+ bitbucket_access_token_secret: "secret"
+ }
+ }
+ end
+ let(:project) do
+ create(
+ :project,
+ import_source: project_identifier,
+ import_data: ProjectImportData.new(data: data)
+ )
+ end
+ let(:importer) { Gitlab::BitbucketImport::Importer.new(project) }
+ let(:issues_statuses_sample_data) do
+ {
+ count: sample_issues_statuses.count,
+ issues: sample_issues_statuses
+ }
+ end
+
+ context 'issues statuses' do
+ before do
+ stub_request(
+ :get,
+ "https://bitbucket.org/api/1.0/repositories/#{project_identifier}"
+ ).to_return(status: 200, body: { has_issues: true }.to_json)
+
+ stub_request(
+ :get,
+ "https://bitbucket.org/api/1.0/repositories/#{project_identifier}/issues?limit=50&sort=utc_created_on&start=0"
+ ).to_return(status: 200, body: issues_statuses_sample_data.to_json)
+
+ sample_issues_statuses.each_with_index do |issue, index|
+ stub_request(
+ :get,
+ "https://bitbucket.org/api/1.0/repositories/#{project_identifier}/issues/#{issue[:local_id]}/comments"
+ ).to_return(
+ status: 200,
+ body: [{ author_info: { username: "username" }, utc_created_on: index }].to_json
+ )
+ end
+ end
+
+ it 'map statuses to open or closed' do
+ importer.execute
+
+ expect(project.issues.where(state: "closed").size).to eq(5)
+ expect(project.issues.where(state: "opened").size).to eq(2)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb
index 04cf11fc6f1..844fd79c991 100644
--- a/spec/lib/gitlab/closing_issue_extractor_spec.rb
+++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb
@@ -11,6 +11,7 @@ describe Gitlab::ClosingIssueExtractor, lib: true do
subject { described_class.new(project, project.creator) }
before do
+ project.team << [project.creator, :developer]
project2.team << [project.creator, :master]
end
diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb
index 0d9694f2c13..a0cbef6e6a4 100644
--- a/spec/lib/gitlab/diff/file_spec.rb
+++ b/spec/lib/gitlab/diff/file_spec.rb
@@ -18,4 +18,18 @@ describe Gitlab::Diff::File, lib: true do
describe :mode_changed? do
it { expect(diff_file.mode_changed?).to be_falsey }
end
+
+ describe '#too_large?' do
+ it 'returns true for a file that is too large' do
+ expect(diff).to receive(:too_large?).and_return(true)
+
+ expect(diff_file.too_large?).to eq(true)
+ end
+
+ it 'returns false for a file that is small enough' do
+ expect(diff).to receive(:too_large?).and_return(false)
+
+ expect(diff_file.too_large?).to eq(false)
+ end
+ end
end
diff --git a/spec/lib/gitlab/diff/parser_spec.rb b/spec/lib/gitlab/diff/parser_spec.rb
index f576c39284e..cdff063a9ed 100644
--- a/spec/lib/gitlab/diff/parser_spec.rb
+++ b/spec/lib/gitlab/diff/parser_spec.rb
@@ -90,4 +90,9 @@ eos
end
end
end
+
+ context 'when lines is empty' do
+ it { expect(parser.parse([])).to eq([]) }
+ it { expect(parser.parse(nil)).to eq([]) }
+ end
end
diff --git a/spec/lib/gitlab/exclusive_lease_spec.rb b/spec/lib/gitlab/exclusive_lease_spec.rb
new file mode 100644
index 00000000000..fbdb7ea34ac
--- /dev/null
+++ b/spec/lib/gitlab/exclusive_lease_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe Gitlab::ExclusiveLease do
+ it 'cannot obtain twice before the lease has expired' do
+ lease = Gitlab::ExclusiveLease.new(unique_key, timeout: 3600)
+ expect(lease.try_obtain).to eq(true)
+ expect(lease.try_obtain).to eq(false)
+ end
+
+ it 'can obtain after the lease has expired' do
+ timeout = 1
+ lease = Gitlab::ExclusiveLease.new(unique_key, timeout: timeout)
+ lease.try_obtain # start the lease
+ sleep(2 * timeout) # lease should have expired now
+ expect(lease.try_obtain).to eq(true)
+ end
+
+ def unique_key
+ SecureRandom.hex(10)
+ end
+end
diff --git a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb
new file mode 100644
index 00000000000..0a7ca3ec848
--- /dev/null
+++ b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb
@@ -0,0 +1,89 @@
+require 'spec_helper'
+
+describe Gitlab::Gfm::ReferenceRewriter do
+ let(:text) { 'some text' }
+ let(:old_project) { create(:project) }
+ let(:new_project) { create(:project) }
+ let(:user) { create(:user) }
+
+ before { old_project.team << [user, :guest] }
+
+ describe '#rewrite' do
+ subject do
+ described_class.new(text, old_project, user).rewrite(new_project)
+ end
+
+ context 'multiple issues and merge requests referenced' do
+ let!(:issue_first) { create(:issue, project: old_project) }
+ let!(:issue_second) { create(:issue, project: old_project) }
+ let!(:merge_request) { create(:merge_request, source_project: old_project) }
+
+ context 'plain text description' do
+ let(:text) { 'Description that references #1, #2 and !1' }
+
+ it { is_expected.to include issue_first.to_reference(new_project) }
+ it { is_expected.to include issue_second.to_reference(new_project) }
+ it { is_expected.to include merge_request.to_reference(new_project) }
+ end
+
+ context 'description with ignored elements' do
+ let(:text) do
+ "Hi. This references #1, but not `#2`\n" +
+ '<pre>and not !1</pre>'
+ end
+
+ it { is_expected.to include issue_first.to_reference(new_project) }
+ it { is_expected.to_not include issue_second.to_reference(new_project) }
+ it { is_expected.to_not include merge_request.to_reference(new_project) }
+ end
+
+ context 'description ambigous elements' do
+ context 'url' do
+ let(:url) { 'http://gitlab.com/#1' }
+ let(:text) { "This references #1, but not #{url}" }
+
+ it { is_expected.to include url }
+ end
+
+ context 'code' do
+ let(:text) { "#1, but not `[#1]`" }
+ it { is_expected.to eq "#{issue_first.to_reference(new_project)}, but not `[#1]`" }
+ end
+
+ context 'code reverse' do
+ let(:text) { "not `#1`, but #1" }
+ it { is_expected.to eq "not `#1`, but #{issue_first.to_reference(new_project)}" }
+ end
+
+ context 'code in random order' do
+ let(:text) { "#1, `#1`, #1, `#1`" }
+ let(:ref) { issue_first.to_reference(new_project) }
+
+ it { is_expected.to eq "#{ref}, `#1`, #{ref}, `#1`" }
+ end
+
+ context 'description with labels' do
+ let!(:label) { create(:label, id: 123, name: 'test', project: old_project) }
+ let(:project_ref) { old_project.to_reference }
+
+ context 'label referenced by id' do
+ let(:text) { '#1 and ~123' }
+ it { is_expected.to eq %Q{#{project_ref}#1 and #{project_ref}~123} }
+ end
+
+ context 'label referenced by text' do
+ let(:text) { '#1 and ~"test"' }
+ it { is_expected.to eq %Q{#{project_ref}#1 and #{project_ref}~123} }
+ end
+ end
+ end
+
+ context 'reference contains milestone' do
+ let(:milestone) { create(:milestone) }
+ let(:text) { "milestone ref: #{milestone.to_reference}" }
+
+ it { is_expected.to eq text }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
index 6cebcb5009a..e49dcb42342 100644
--- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
@@ -127,34 +127,6 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end
end
- describe '#cross_project?' do
- context 'when source, and target repositories are the same' do
- let(:raw_data) { OpenStruct.new(base_data) }
-
- it 'returns false' do
- expect(pull_request.cross_project?).to eq false
- end
- end
-
- context 'when source repo is a fork' do
- let(:source_repo) { OpenStruct.new(id: 2, fork: true) }
- let(:raw_data) { OpenStruct.new(base_data) }
-
- it 'returns true' do
- expect(pull_request.cross_project?).to eq true
- end
- end
-
- context 'when target repo is a fork' do
- let(:target_repo) { OpenStruct.new(id: 2, fork: true) }
- let(:raw_data) { OpenStruct.new(base_data) }
-
- it 'returns true' do
- expect(pull_request.cross_project?).to eq true
- end
- end
- end
-
describe '#number' do
let(:raw_data) { OpenStruct.new(base_data.merge(number: 1347)) }
@@ -166,24 +138,44 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
describe '#valid?' do
let(:invalid_branch) { OpenStruct.new(ref: 'invalid-branch') }
- context 'when source and target branches exists' do
- let(:raw_data) { OpenStruct.new(base_data.merge(head: source_branch, base: target_branch)) }
+ context 'when source, and target repositories are the same' do
+ context 'and source and target branches exists' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(head: source_branch, base: target_branch)) }
- it 'returns true' do
- expect(pull_request.valid?).to eq true
+ it 'returns true' do
+ expect(pull_request.valid?).to eq true
+ end
+ end
+
+ context 'and source branch doesn not exists' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(head: invalid_branch, base: target_branch)) }
+
+ it 'returns false' do
+ expect(pull_request.valid?).to eq false
+ end
+ end
+
+ context 'and target branch doesn not exists' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(head: source_branch, base: invalid_branch)) }
+
+ it 'returns false' do
+ expect(pull_request.valid?).to eq false
+ end
end
end
- context 'when source branch doesn not exists' do
- let(:raw_data) { OpenStruct.new(base_data.merge(head: invalid_branch, base: target_branch)) }
+ context 'when source repo is a fork' do
+ let(:source_repo) { OpenStruct.new(id: 2, fork: true) }
+ let(:raw_data) { OpenStruct.new(base_data) }
it 'returns false' do
expect(pull_request.valid?).to eq false
end
end
- context 'when target branch doesn not exists' do
- let(:raw_data) { OpenStruct.new(base_data.merge(head: source_branch, base: invalid_branch)) }
+ context 'when target repo is a fork' do
+ let(:target_repo) { OpenStruct.new(id: 2, fork: true) }
+ let(:raw_data) { OpenStruct.new(base_data) }
it 'returns false' do
expect(pull_request.valid?).to eq false
diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb
new file mode 100644
index 00000000000..117a15264da
--- /dev/null
+++ b/spec/lib/gitlab/middleware/go_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+describe Gitlab::Middleware::Go, lib: true do
+ let(:app) { double(:app) }
+ let(:middleware) { described_class.new(app) }
+
+ describe '#call' do
+ describe 'when go-get=0' do
+ it 'skips go-import generation' do
+ env = { 'rack.input' => '',
+ 'QUERY_STRING' => 'go-get=0' }
+ expect(app).to receive(:call).with(env).and_return('no-go')
+ middleware.call(env)
+ end
+ end
+
+ describe 'when go-get=1' do
+ it 'returns a document' do
+ env = { 'rack.input' => '',
+ 'QUERY_STRING' => 'go-get=1',
+ 'PATH_INFO' => '/group/project/path' }
+ resp = middleware.call(env)
+ expect(resp[0]).to eq(200)
+ expect(resp[1]['Content-Type']).to eq('text/html')
+ expected_body = "<!DOCTYPE html><html><head><meta content='localhost/group/project git http://localhost/group/project.git' name='go-import'></head></html>\n"
+ expect(resp[2].body).to eq([expected_body])
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb
index efc2e5f4ef1..db0ff95b4f5 100644
--- a/spec/lib/gitlab/project_search_results_spec.rb
+++ b/spec/lib/gitlab/project_search_results_spec.rb
@@ -1,11 +1,12 @@
require 'spec_helper'
describe Gitlab::ProjectSearchResults, lib: true do
+ let(:user) { create(:user) }
let(:project) { create(:project) }
let(:query) { 'hello world' }
describe 'initialize with empty ref' do
- let(:results) { Gitlab::ProjectSearchResults.new(project.id, query, '') }
+ let(:results) { Gitlab::ProjectSearchResults.new(user, project, query, '') }
it { expect(results.project).to eq(project) }
it { expect(results.repository_ref).to be_nil }
@@ -14,10 +15,74 @@ describe Gitlab::ProjectSearchResults, lib: true do
describe 'initialize with ref' do
let(:ref) { 'refs/heads/test' }
- let(:results) { Gitlab::ProjectSearchResults.new(project.id, query, ref) }
+ let(:results) { Gitlab::ProjectSearchResults.new(user, project, query, ref) }
it { expect(results.project).to eq(project) }
it { expect(results.repository_ref).to eq(ref) }
it { expect(results.query).to eq('hello world') }
end
+
+ describe 'confidential issues' do
+ let(:query) { 'issue' }
+ let(:author) { create(:user) }
+ let(:assignee) { create(:user) }
+ let(:non_member) { create(:user) }
+ let(:member) { create(:user) }
+ let(:admin) { create(:admin) }
+ let!(:issue) { create(:issue, project: project, title: 'Issue 1') }
+ let!(:security_issue_1) { create(:issue, :confidential, project: project, title: 'Security issue 1', author: author) }
+ let!(:security_issue_2) { create(:issue, :confidential, title: 'Security issue 2', project: project, assignee: assignee) }
+
+ it 'should not list project confidential issues for non project members' do
+ results = described_class.new(non_member, project, query)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).not_to include security_issue_1
+ expect(issues).not_to include security_issue_2
+ expect(results.issues_count).to eq 1
+ end
+
+ it 'should list project confidential issues for author' do
+ results = described_class.new(author, project, query)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).to include security_issue_1
+ expect(issues).not_to include security_issue_2
+ expect(results.issues_count).to eq 2
+ end
+
+ it 'should list project confidential issues for assignee' do
+ results = described_class.new(assignee, project.id, query)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).not_to include security_issue_1
+ expect(issues).to include security_issue_2
+ expect(results.issues_count).to eq 2
+ end
+
+ it 'should list project confidential issues for project members' do
+ project.team << [member, :developer]
+
+ results = described_class.new(member, project, query)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).to include security_issue_1
+ expect(issues).to include security_issue_2
+ expect(results.issues_count).to eq 3
+ end
+
+ it 'should list all project issues for admin' do
+ results = described_class.new(admin, project, query)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).to include security_issue_1
+ expect(issues).to include security_issue_2
+ expect(results.issues_count).to eq 3
+ end
+ end
end
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index 7d963795e17..7c617723e6d 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
describe Gitlab::ReferenceExtractor, lib: true do
let(:project) { create(:project) }
+
subject { Gitlab::ReferenceExtractor.new(project, project.creator) }
it 'accesses valid user objects' do
@@ -41,6 +42,7 @@ describe Gitlab::ReferenceExtractor, lib: true do
end
it 'accesses valid issue objects' do
+ project.team << [project.creator, :developer]
@i0 = create(:issue, project: project)
@i1 = create(:issue, project: project)
@@ -122,4 +124,24 @@ describe Gitlab::ReferenceExtractor, lib: true do
expect(extracted).to match_array([issue])
end
end
+
+ describe '#all' do
+ let(:issue) { create(:issue, project: project) }
+ let(:label) { create(:label, project: project) }
+ let(:text) { "Ref. #{issue.to_reference} and #{label.to_reference}" }
+
+ before do
+ project.team << [project.creator, :developer]
+ subject.analyze(text)
+ end
+
+ it 'returns all referables' do
+ expect(subject.all).to match_array([issue, label])
+ end
+ end
+
+ describe '.references_pattern' do
+ subject { described_class.references_pattern }
+ it { is_expected.to be_kind_of Regexp }
+ end
end
diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb
new file mode 100644
index 00000000000..f4afe597e8d
--- /dev/null
+++ b/spec/lib/gitlab/search_results_spec.rb
@@ -0,0 +1,144 @@
+require 'spec_helper'
+
+describe Gitlab::SearchResults do
+ let(:user) { create(:user) }
+ let!(:project) { create(:project, name: 'foo') }
+ let!(:issue) { create(:issue, project: project, title: 'foo') }
+
+ let!(:merge_request) do
+ create(:merge_request, source_project: project, title: 'foo')
+ end
+
+ let!(:milestone) { create(:milestone, project: project, title: 'foo') }
+ let(:results) { described_class.new(user, Project.all, 'foo') }
+
+ describe '#total_count' do
+ it 'returns the total amount of search hits' do
+ expect(results.total_count).to eq(4)
+ end
+ end
+
+ describe '#projects_count' do
+ it 'returns the total amount of projects' do
+ expect(results.projects_count).to eq(1)
+ end
+ end
+
+ describe '#issues_count' do
+ it 'returns the total amount of issues' do
+ expect(results.issues_count).to eq(1)
+ end
+ end
+
+ describe '#merge_requests_count' do
+ it 'returns the total amount of merge requests' do
+ expect(results.merge_requests_count).to eq(1)
+ end
+ end
+
+ describe '#milestones_count' do
+ it 'returns the total amount of milestones' do
+ expect(results.milestones_count).to eq(1)
+ end
+ end
+
+ describe '#empty?' do
+ it 'returns true when there are no search results' do
+ allow(results).to receive(:total_count).and_return(0)
+
+ expect(results.empty?).to eq(true)
+ end
+
+ it 'returns false when there are search results' do
+ expect(results.empty?).to eq(false)
+ end
+ end
+
+ describe 'confidential issues' do
+ let(:project_1) { create(:empty_project) }
+ let(:project_2) { create(:empty_project) }
+ let(:project_3) { create(:empty_project) }
+ let(:project_4) { create(:empty_project) }
+ let(:query) { 'issue' }
+ let(:limit_projects) { Project.where(id: [project_1.id, project_2.id, project_3.id]) }
+ let(:author) { create(:user) }
+ let(:assignee) { create(:user) }
+ let(:non_member) { create(:user) }
+ let(:member) { create(:user) }
+ let(:admin) { create(:admin) }
+ let!(:issue) { create(:issue, project: project_1, title: 'Issue 1') }
+ let!(:security_issue_1) { create(:issue, :confidential, project: project_1, title: 'Security issue 1', author: author) }
+ let!(:security_issue_2) { create(:issue, :confidential, title: 'Security issue 2', project: project_1, assignee: assignee) }
+ let!(:security_issue_3) { create(:issue, :confidential, project: project_2, title: 'Security issue 3', author: author) }
+ let!(:security_issue_4) { create(:issue, :confidential, project: project_3, title: 'Security issue 4', assignee: assignee) }
+ let!(:security_issue_5) { create(:issue, :confidential, project: project_4, title: 'Security issue 5') }
+
+ it 'should not list confidential issues for non project members' do
+ results = described_class.new(non_member, limit_projects, query)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).not_to include security_issue_1
+ expect(issues).not_to include security_issue_2
+ expect(issues).not_to include security_issue_3
+ expect(issues).not_to include security_issue_4
+ expect(issues).not_to include security_issue_5
+ expect(results.issues_count).to eq 1
+ end
+
+ it 'should list confidential issues for author' do
+ results = described_class.new(author, limit_projects, query)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).to include security_issue_1
+ expect(issues).not_to include security_issue_2
+ expect(issues).to include security_issue_3
+ expect(issues).not_to include security_issue_4
+ expect(issues).not_to include security_issue_5
+ expect(results.issues_count).to eq 3
+ end
+
+ it 'should list confidential issues for assignee' do
+ results = described_class.new(assignee, limit_projects, query)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).not_to include security_issue_1
+ expect(issues).to include security_issue_2
+ expect(issues).not_to include security_issue_3
+ expect(issues).to include security_issue_4
+ expect(issues).not_to include security_issue_5
+ expect(results.issues_count).to eq 3
+ end
+
+ it 'should list confidential issues for project members' do
+ project_1.team << [member, :developer]
+ project_2.team << [member, :developer]
+
+ results = described_class.new(member, limit_projects, query)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).to include security_issue_1
+ expect(issues).to include security_issue_2
+ expect(issues).to include security_issue_3
+ expect(issues).not_to include security_issue_4
+ expect(issues).not_to include security_issue_5
+ expect(results.issues_count).to eq 4
+ end
+
+ it 'should list all issues for admin' do
+ results = described_class.new(admin, limit_projects, query)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).to include security_issue_1
+ expect(issues).to include security_issue_2
+ expect(issues).to include security_issue_3
+ expect(issues).to include security_issue_4
+ expect(issues).not_to include security_issue_5
+ expect(results.issues_count).to eq 5
+ end
+ end
+end
diff --git a/spec/lib/gitlab/snippet_search_results_spec.rb b/spec/lib/gitlab/snippet_search_results_spec.rb
new file mode 100644
index 00000000000..e86b9ef6a63
--- /dev/null
+++ b/spec/lib/gitlab/snippet_search_results_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe Gitlab::SnippetSearchResults do
+ let!(:snippet) { create(:snippet, content: 'foo', file_name: 'foo') }
+
+ let(:results) { described_class.new(Snippet.all, 'foo') }
+
+ describe '#total_count' do
+ it 'returns the total amount of search hits' do
+ expect(results.total_count).to eq(2)
+ end
+ end
+
+ describe '#snippet_titles_count' do
+ it 'returns the amount of matched snippet titles' do
+ expect(results.snippet_titles_count).to eq(1)
+ end
+ end
+
+ describe '#snippet_blobs_count' do
+ it 'returns the amount of matched snippet blobs' do
+ expect(results.snippet_blobs_count).to eq(1)
+ end
+ end
+end
diff --git a/spec/mailers/emails/profile_spec.rb b/spec/mailers/emails/profile_spec.rb
index 5b575da34f3..c6758ccad39 100644
--- a/spec/mailers/emails/profile_spec.rb
+++ b/spec/mailers/emails/profile_spec.rb
@@ -11,7 +11,7 @@ describe Notify do
let(:example_site_path) { root_path }
let(:new_user) { create(:user, email: new_user_address, created_by_id: 1) }
let(:token) { 'kETLwRaayvigPq_x3SNM' }
-
+
subject { Notify.new_user_email(new_user.id, token) }
it_behaves_like 'an email sent from GitLab'
@@ -77,6 +77,10 @@ describe Notify do
it 'includes a link to ssh keys page' do
is_expected.to have_body_text /#{profile_keys_path}/
end
+
+ context 'with SSH key that does not exist' do
+ it { expect { Notify.new_ssh_key_email('foo') }.not_to raise_error }
+ end
end
describe 'user added email' do
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 232a11245a6..9b47acfe0cd 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -100,6 +100,34 @@ describe Notify do
end
end
+ describe 'that have been relabeled' do
+ subject { Notify.relabeled_issue_email(recipient.id, issue.id, %w[foo bar baz], current_user.id) }
+
+ it_behaves_like 'a multiple recipients email'
+ it_behaves_like 'an answer to an existing thread', 'issue'
+ it_behaves_like 'it should show Gmail Actions View Issue link'
+ it_behaves_like 'a user cannot unsubscribe through footer link'
+ it_behaves_like 'an email with a labels subscriptions link in its footer'
+
+ it 'is sent as the author' do
+ sender = subject.header[:from].addrs[0]
+ expect(sender.display_name).to eq(current_user.name)
+ expect(sender.address).to eq(gitlab_sender)
+ end
+
+ it 'has the correct subject' do
+ is_expected.to have_subject /#{issue.title} \(##{issue.iid}\)/
+ end
+
+ it 'contains the names of the added labels' do
+ is_expected.to have_body_text /foo, bar, and baz/
+ end
+
+ it 'contains a link to the issue' do
+ is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/
+ end
+ end
+
describe 'status changed' do
let(:status) { 'closed' }
subject { Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user.id) }
@@ -130,6 +158,33 @@ describe Notify do
is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/
end
end
+
+ describe 'moved to another project' do
+ let(:new_issue) { create(:issue) }
+ subject { Notify.issue_moved_email(recipient, issue, new_issue, current_user) }
+
+ it_behaves_like 'an answer to an existing thread', 'issue'
+ it_behaves_like 'it should show Gmail Actions View Issue link'
+ it_behaves_like 'an unsubscribeable thread'
+
+ it 'contains description about action taken' do
+ is_expected.to have_body_text 'Issue was moved to another project'
+ end
+
+ it 'has the correct subject' do
+ is_expected.to have_subject /#{issue.title} \(##{issue.iid}\)/i
+ end
+
+ it 'contains link to new issue' do
+ new_issue_url = namespace_project_issue_path(new_issue.project.namespace,
+ new_issue.project, new_issue)
+ is_expected.to have_body_text new_issue_url
+ end
+
+ it 'contains a link to the original issue' do
+ is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/
+ end
+ end
end
context 'for merge requests' do
@@ -219,6 +274,34 @@ describe Notify do
end
end
+ describe 'that have been relabeled' do
+ subject { Notify.relabeled_merge_request_email(recipient.id, merge_request.id, %w[foo bar baz], current_user.id) }
+
+ it_behaves_like 'a multiple recipients email'
+ it_behaves_like 'an answer to an existing thread', 'merge_request'
+ it_behaves_like 'it should show Gmail Actions View Merge request link'
+ it_behaves_like 'a user cannot unsubscribe through footer link'
+ it_behaves_like 'an email with a labels subscriptions link in its footer'
+
+ it 'is sent as the author' do
+ sender = subject.header[:from].addrs[0]
+ expect(sender.display_name).to eq(current_user.name)
+ expect(sender.address).to eq(gitlab_sender)
+ end
+
+ it 'has the correct subject' do
+ is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/
+ end
+
+ it 'contains the names of the added labels' do
+ is_expected.to have_body_text /foo, bar, and baz/
+ end
+
+ it 'contains a link to the merge request' do
+ is_expected.to have_body_text /#{namespace_project_merge_request_path project.namespace, project, merge_request}/
+ end
+ end
+
describe 'status changed' do
let(:status) { 'reopened' }
subject { Notify.merge_request_status_email(recipient.id, merge_request.id, status, current_user.id) }
diff --git a/spec/mailers/shared/notify.rb b/spec/mailers/shared/notify.rb
index 48c851ebbd6..6019af544d3 100644
--- a/spec/mailers/shared/notify.rb
+++ b/spec/mailers/shared/notify.rb
@@ -112,6 +112,10 @@ shared_examples 'an unsubscribeable thread' do
it { is_expected.to have_body_text /unsubscribe/ }
end
-shared_examples "a user cannot unsubscribe through footer link" do
+shared_examples 'a user cannot unsubscribe through footer link' do
it { is_expected.not_to have_body_text /unsubscribe/ }
end
+
+shared_examples 'an email with a labels subscriptions link in its footer' do
+ it { is_expected.to have_body_text /label subscriptions/ }
+end
diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb
index 4799bbaa57c..ac12ab6c757 100644
--- a/spec/models/abuse_report_spec.rb
+++ b/spec/models/abuse_report_spec.rb
@@ -13,7 +13,8 @@
require 'rails_helper'
RSpec.describe AbuseReport, type: :model do
- subject { create(:abuse_report) }
+ subject { create(:abuse_report) }
+ let(:user) { create(:user) }
it { expect(subject).to be_valid }
@@ -31,17 +32,14 @@ RSpec.describe AbuseReport, type: :model do
describe '#remove_user' do
it 'blocks the user' do
- report = build(:abuse_report)
-
- allow(report.user).to receive(:destroy)
-
- expect { report.remove_user }.to change { report.user.blocked? }.to(true)
+ expect { subject.remove_user(deleted_by: user) }.to change { subject.user.blocked? }.to(true)
end
- it 'removes the user' do
- report = build(:abuse_report)
+ it 'lets a worker delete the user' do
+ expect(DeleteUserWorker).to receive(:perform_async).with(user.id, subject.user.id,
+ delete_solo_owned_groups: true)
- expect { report.remove_user }.to change { User.count }.by(-1)
+ subject.remove_user(deleted_by: user)
end
end
diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb
index e3d3d453653..b7457808040 100644
--- a/spec/models/build_spec.rb
+++ b/spec/models/build_spec.rb
@@ -9,7 +9,7 @@ describe Ci::Build, models: true do
it { is_expected.to respond_to :trace_html }
- describe :first_pending do
+ describe '#first_pending' do
let(:first) { FactoryGirl.create :ci_build, commit: commit, status: 'pending', created_at: Date.yesterday }
let(:second) { FactoryGirl.create :ci_build, commit: commit, status: 'pending' }
before { first; second }
@@ -19,7 +19,7 @@ describe Ci::Build, models: true do
it('returns with the first pending build') { is_expected.to eq(first) }
end
- describe :create_from do
+ describe '#create_from' do
before do
build.status = 'success'
build.save
@@ -33,7 +33,7 @@ describe Ci::Build, models: true do
end
end
- describe :ignored? do
+ describe '#ignored?' do
subject { build.ignored? }
context 'if build is not allowed to fail' do
@@ -69,7 +69,7 @@ describe Ci::Build, models: true do
end
end
- describe :trace do
+ describe '#trace' do
subject { build.trace_html }
it { is_expected.to be_empty }
@@ -101,7 +101,7 @@ describe Ci::Build, models: true do
# it { is_expected.to eq(commit.project.timeout) }
# end
- describe :options do
+ describe '#options' do
let(:options) do
{
image: "ruby:2.1",
@@ -122,25 +122,25 @@ describe Ci::Build, models: true do
# it { is_expected.to eq(project.allow_git_fetch) }
# end
- describe :project do
+ describe '#project' do
subject { build.project }
it { is_expected.to eq(commit.project) }
end
- describe :project_id do
+ describe '#project_id' do
subject { build.project_id }
it { is_expected.to eq(commit.project_id) }
end
- describe :project_name do
+ describe '#project_name' do
subject { build.project_name }
it { is_expected.to eq(project.name) }
end
- describe :extract_coverage do
+ describe '#extract_coverage' do
context 'valid content & regex' do
subject { build.extract_coverage('Coverage 1033 / 1051 LOC (98.29%) covered', '\(\d+.\d+\%\) covered') }
@@ -172,7 +172,7 @@ describe Ci::Build, models: true do
end
end
- describe :variables do
+ describe '#variables' do
context 'returns variables' do
subject { build.variables }
@@ -242,7 +242,7 @@ describe Ci::Build, models: true do
end
end
- describe :can_be_served? do
+ describe '#can_be_served?' do
let(:runner) { FactoryGirl.create :ci_runner }
before { build.project.runners << runner }
@@ -277,7 +277,7 @@ describe Ci::Build, models: true do
end
end
- describe :any_runners_online? do
+ describe '#any_runners_online?' do
subject { build.any_runners_online? }
context 'when no runners' do
@@ -312,8 +312,8 @@ describe Ci::Build, models: true do
end
end
- describe :show_warning? do
- subject { build.show_warning? }
+ describe '#stuck?' do
+ subject { build.stuck? }
%w(pending).each do |state|
context "if commit_status.status is #{state}" do
@@ -343,35 +343,7 @@ describe Ci::Build, models: true do
end
end
- describe :artifacts_download_url do
- subject { build.artifacts_download_url }
-
- context 'artifacts file does not exist' do
- before { build.update_attributes(artifacts_file: nil) }
- it { is_expected.to be_nil }
- end
-
- context 'artifacts file exists' do
- let(:build) { create(:ci_build, :artifacts) }
- it { is_expected.to_not be_nil }
- end
- end
-
- describe :artifacts_browse_url do
- subject { build.artifacts_browse_url }
-
- it "should be nil if artifacts browser is unsupported" do
- allow(build).to receive(:artifacts_metadata?).and_return(false)
- is_expected.to be_nil
- end
-
- it 'should not be nil if artifacts browser is supported' do
- allow(build).to receive(:artifacts_metadata?).and_return(true)
- is_expected.to_not be_nil
- end
- end
-
- describe :artifacts? do
+ describe '#artifacts?' do
subject { build.artifacts? }
context 'artifacts archive does not exist' do
@@ -386,7 +358,7 @@ describe Ci::Build, models: true do
end
- describe :artifacts_metadata? do
+ describe '#artifacts_metadata?' do
subject { build.artifacts_metadata? }
context 'artifacts metadata does not exist' do
it { is_expected.to be_falsy }
@@ -398,7 +370,7 @@ describe Ci::Build, models: true do
end
end
- describe :repo_url do
+ describe '#repo_url' do
let(:build) { FactoryGirl.create :ci_build }
let(:project) { build.project }
@@ -412,7 +384,7 @@ describe Ci::Build, models: true do
it { is_expected.to include(project.web_url[7..-1]) }
end
- describe :depends_on_builds do
+ describe '#depends_on_builds' do
let!(:build) { FactoryGirl.create :ci_build, commit: commit, name: 'build', stage_idx: 0, stage: 'build' }
let!(:rspec_test) { FactoryGirl.create :ci_build, commit: commit, name: 'rspec', stage_idx: 1, stage: 'test' }
let!(:rubocop_test) { FactoryGirl.create :ci_build, commit: commit, name: 'rubocop', stage_idx: 1, stage: 'test' }
@@ -444,7 +416,7 @@ describe Ci::Build, models: true do
created_at: created_at)
end
- describe :merge_request do
+ describe '#merge_request' do
context 'when a MR has a reference to the commit' do
before do
@merge_request = create_mr(build, commit, factory: :merge_request)
diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb
index 4dc309a4255..412842337ba 100644
--- a/spec/models/ci/commit_spec.rb
+++ b/spec/models/ci/commit_spec.rb
@@ -32,50 +32,6 @@ describe Ci::Commit, models: true do
it { is_expected.to respond_to :git_author_email }
it { is_expected.to respond_to :short_sha }
- describe :ordered do
- let(:project) { FactoryGirl.create :empty_project }
-
- it 'returns ordered list of commits' do
- commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, project: project
- commit2 = FactoryGirl.create :ci_commit, committed_at: 2.hours.ago, project: project
- expect(project.ci_commits.ordered).to eq([commit2, commit1])
- end
-
- it 'returns commits ordered by committed_at and id, with nulls last' do
- commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, project: project
- commit2 = FactoryGirl.create :ci_commit, committed_at: nil, project: project
- commit3 = FactoryGirl.create :ci_commit, committed_at: 2.hours.ago, project: project
- commit4 = FactoryGirl.create :ci_commit, committed_at: nil, project: project
- expect(project.ci_commits.ordered).to eq([commit2, commit4, commit3, commit1])
- end
- end
-
- describe :last_build do
- subject { commit.last_build }
- before do
- @first = FactoryGirl.create :ci_build, commit: commit, created_at: Date.yesterday
- @second = FactoryGirl.create :ci_build, commit: commit
- end
-
- it { is_expected.to be_a(Ci::Build) }
- it('returns with the most recently created build') { is_expected.to eq(@second) }
- end
-
- describe :retry do
- before do
- @first = FactoryGirl.create :ci_build, commit: commit, created_at: Date.yesterday
- @second = FactoryGirl.create :ci_build, commit: commit
- end
-
- it "creates only a new build" do
- expect(commit.builds.count(:all)).to eq 2
- expect(commit.statuses.count(:all)).to eq 2
- commit.retry
- expect(commit.builds.count(:all)).to eq 3
- expect(commit.statuses.count(:all)).to eq 3
- end
- end
-
describe :valid_commit_sha do
context 'commit.sha can not start with 00000000' do
before do
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index e891838672e..25e9e5eca48 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -132,4 +132,32 @@ describe Ci::Runner, models: true do
expect(runner.belongs_to_one_project?).to be_truthy
end
end
+
+ describe '#search' do
+ let(:runner) { create(:ci_runner, token: '123abc') }
+
+ it 'returns runners with a matching token' do
+ expect(described_class.search(runner.token)).to eq([runner])
+ end
+
+ it 'returns runners with a partially matching token' do
+ expect(described_class.search(runner.token[0..2])).to eq([runner])
+ end
+
+ it 'returns runners with a matching token regardless of the casing' do
+ expect(described_class.search(runner.token.upcase)).to eq([runner])
+ end
+
+ it 'returns runners with a matching description' do
+ expect(described_class.search(runner.description)).to eq([runner])
+ end
+
+ it 'returns runners with a partially matching description' do
+ expect(described_class.search(runner.description[0..2])).to eq([runner])
+ end
+
+ it 'returns runners with a matching description regardless of the casing' do
+ expect(described_class.search(runner.description.upcase)).to eq([runner])
+ end
+ end
end
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 253902512c3..0e9111c8029 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -86,10 +86,21 @@ eos
let(:issue) { create :issue, project: project }
let(:other_project) { create :project, :public }
let(:other_issue) { create :issue, project: other_project }
+ let(:commiter) { create :user }
+
+ before do
+ project.team << [commiter, :developer]
+ other_project.team << [commiter, :developer]
+ end
it 'detects issues that this commit is marked as closing' do
ext_ref = "#{other_project.path_with_namespace}##{other_issue.iid}"
- allow(commit).to receive(:safe_message).and_return("Fixes ##{issue.iid} and #{ext_ref}")
+
+ allow(commit).to receive_messages(
+ safe_message: "Fixes ##{issue.iid} and #{ext_ref}",
+ committer_email: commiter.email
+ )
+
expect(commit.closes_issues).to include(issue)
expect(commit.closes_issues).to include(other_issue)
end
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 600089802b2..be29b6d66ff 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -32,9 +32,54 @@ describe Issue, "Issuable" do
describe ".search" do
let!(:searchable_issue) { create(:issue, title: "Searchable issue") }
- it "matches by title" do
+ it 'returns notes with a matching title' do
+ expect(described_class.search(searchable_issue.title)).
+ to eq([searchable_issue])
+ end
+
+ it 'returns notes with a partially matching title' do
expect(described_class.search('able')).to eq([searchable_issue])
end
+
+ it 'returns notes with a matching title regardless of the casing' do
+ expect(described_class.search(searchable_issue.title.upcase)).
+ to eq([searchable_issue])
+ end
+ end
+
+ describe ".full_search" do
+ let!(:searchable_issue) do
+ create(:issue, title: "Searchable issue", description: 'kittens')
+ end
+
+ it 'returns notes with a matching title' do
+ expect(described_class.full_search(searchable_issue.title)).
+ to eq([searchable_issue])
+ end
+
+ it 'returns notes with a partially matching title' do
+ expect(described_class.full_search('able')).to eq([searchable_issue])
+ end
+
+ it 'returns notes with a matching title regardless of the casing' do
+ expect(described_class.full_search(searchable_issue.title.upcase)).
+ to eq([searchable_issue])
+ end
+
+ it 'returns notes with a matching description' do
+ expect(described_class.full_search(searchable_issue.description)).
+ to eq([searchable_issue])
+ end
+
+ it 'returns notes with a partially matching description' do
+ expect(described_class.full_search(searchable_issue.description)).
+ to eq([searchable_issue])
+ end
+
+ it 'returns notes with a matching description regardless of the casing' do
+ expect(described_class.full_search(searchable_issue.description.upcase)).
+ to eq([searchable_issue])
+ end
end
describe "#today?" do
@@ -68,6 +113,48 @@ describe Issue, "Issuable" do
end
end
+ describe '#subscribed?' do
+ context 'user is not a participant in the issue' do
+ before { allow(issue).to receive(:participants).with(user).and_return([]) }
+
+ it 'returns false when no subcription exists' do
+ expect(issue.subscribed?(user)).to be_falsey
+ end
+
+ it 'returns true when a subcription exists and subscribed is true' do
+ issue.subscriptions.create(user: user, subscribed: true)
+
+ expect(issue.subscribed?(user)).to be_truthy
+ end
+
+ it 'returns false when a subcription exists and subscribed is false' do
+ issue.subscriptions.create(user: user, subscribed: false)
+
+ expect(issue.subscribed?(user)).to be_falsey
+ end
+ end
+
+ context 'user is a participant in the issue' do
+ before { allow(issue).to receive(:participants).with(user).and_return([user]) }
+
+ it 'returns false when no subcription exists' do
+ expect(issue.subscribed?(user)).to be_truthy
+ end
+
+ it 'returns true when a subcription exists and subscribed is true' do
+ issue.subscriptions.create(user: user, subscribed: true)
+
+ expect(issue.subscribed?(user)).to be_truthy
+ end
+
+ it 'returns false when a subcription exists and subscribed is false' do
+ issue.subscriptions.create(user: user, subscribed: false)
+
+ expect(issue.subscribed?(user)).to be_falsey
+ end
+ end
+ end
+
describe "#to_hook_data" do
let(:data) { issue.to_hook_data(user) }
let(:project) { issue.project }
diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb
index 20f0c561e44..cb33edde820 100644
--- a/spec/models/concerns/mentionable_spec.rb
+++ b/spec/models/concerns/mentionable_spec.rb
@@ -48,7 +48,8 @@ describe Issue, "Mentionable" do
describe '#create_new_cross_references!' do
let(:project) { create(:project) }
- let(:issues) { create_list(:issue, 2, project: project) }
+ let(:author) { create(:author) }
+ let(:issues) { create_list(:issue, 2, project: project, author: author) }
context 'before changes are persisted' do
it 'ignores pre-existing references' do
@@ -91,7 +92,7 @@ describe Issue, "Mentionable" do
end
def create_issue(description:)
- create(:issue, project: project, description: description)
+ create(:issue, project: project, description: description, author: author)
end
end
end
diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb
new file mode 100644
index 00000000000..47c3be673c5
--- /dev/null
+++ b/spec/models/concerns/milestoneish_spec.rb
@@ -0,0 +1,104 @@
+require 'spec_helper'
+
+describe Milestone, 'Milestoneish' do
+ let(:author) { create(:user) }
+ let(:assignee) { create(:user) }
+ let(:non_member) { create(:user) }
+ let(:member) { create(:user) }
+ let(:admin) { create(:admin) }
+ let(:project) { create(:project, :public) }
+ let(:milestone) { create(:milestone, project: project) }
+ let!(:issue) { create(:issue, project: project, milestone: milestone) }
+ let!(:security_issue_1) { create(:issue, :confidential, project: project, author: author, milestone: milestone) }
+ let!(:security_issue_2) { create(:issue, :confidential, project: project, assignee: assignee, milestone: milestone) }
+ let!(:closed_issue_1) { create(:issue, :closed, project: project, milestone: milestone) }
+ let!(:closed_issue_2) { create(:issue, :closed, project: project, milestone: milestone) }
+ let!(:closed_security_issue_1) { create(:issue, :confidential, :closed, project: project, author: author, milestone: milestone) }
+ let!(:closed_security_issue_2) { create(:issue, :confidential, :closed, project: project, assignee: assignee, milestone: milestone) }
+ let!(:closed_security_issue_3) { create(:issue, :confidential, :closed, project: project, author: author, milestone: milestone) }
+ let!(:closed_security_issue_4) { create(:issue, :confidential, :closed, project: project, assignee: assignee, milestone: milestone) }
+ let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, milestone: milestone) }
+
+ before do
+ project.team << [member, :developer]
+ end
+
+ describe '#closed_items_count' do
+ it 'should not count confidential issues for non project members' do
+ expect(milestone.closed_items_count(non_member)).to eq 2
+ end
+
+ it 'should count confidential issues for author' do
+ expect(milestone.closed_items_count(author)).to eq 4
+ end
+
+ it 'should count confidential issues for assignee' do
+ expect(milestone.closed_items_count(assignee)).to eq 4
+ end
+
+ it 'should count confidential issues for project members' do
+ expect(milestone.closed_items_count(member)).to eq 6
+ end
+
+ it 'should count all issues for admin' do
+ expect(milestone.closed_items_count(admin)).to eq 6
+ end
+ end
+
+ describe '#total_items_count' do
+ it 'should not count confidential issues for non project members' do
+ expect(milestone.total_items_count(non_member)).to eq 4
+ end
+
+ it 'should count confidential issues for author' do
+ expect(milestone.total_items_count(author)).to eq 7
+ end
+
+ it 'should count confidential issues for assignee' do
+ expect(milestone.total_items_count(assignee)).to eq 7
+ end
+
+ it 'should count confidential issues for project members' do
+ expect(milestone.total_items_count(member)).to eq 10
+ end
+
+ it 'should count all issues for admin' do
+ expect(milestone.total_items_count(admin)).to eq 10
+ end
+ end
+
+ describe '#complete?' do
+ it 'returns false when has items opened' do
+ expect(milestone.complete?(non_member)).to eq false
+ end
+
+ it 'returns true when all items are closed' do
+ issue.close
+ merge_request.close
+
+ expect(milestone.complete?(non_member)).to eq true
+ end
+ end
+
+ describe '#percent_complete' do
+ it 'should not count confidential issues for non project members' do
+ expect(milestone.percent_complete(non_member)).to eq 50
+ end
+
+ it 'should count confidential issues for author' do
+ expect(milestone.percent_complete(author)).to eq 57
+ end
+
+ it 'should count confidential issues for assignee' do
+ expect(milestone.percent_complete(assignee)).to eq 57
+ end
+
+ it 'should count confidential issues for project members' do
+ expect(milestone.percent_complete(member)).to eq 60
+ end
+
+ it 'should count confidential issues for admin' do
+ expect(milestone.percent_complete(admin)).to eq 60
+ end
+ end
+end
diff --git a/spec/models/concerns/subscribable_spec.rb b/spec/models/concerns/subscribable_spec.rb
new file mode 100644
index 00000000000..e31fdb0bffb
--- /dev/null
+++ b/spec/models/concerns/subscribable_spec.rb
@@ -0,0 +1,57 @@
+require 'spec_helper'
+
+describe Subscribable, 'Subscribable' do
+ let(:resource) { create(:issue) }
+ let(:user) { create(:user) }
+
+ describe '#subscribed?' do
+ it 'returns false when no subcription exists' do
+ expect(resource.subscribed?(user)).to be_falsey
+ end
+
+ it 'returns true when a subcription exists and subscribed is true' do
+ resource.subscriptions.create(user: user, subscribed: true)
+
+ expect(resource.subscribed?(user)).to be_truthy
+ end
+
+ it 'returns false when a subcription exists and subscribed is false' do
+ resource.subscriptions.create(user: user, subscribed: false)
+
+ expect(resource.subscribed?(user)).to be_falsey
+ end
+ end
+ describe '#subscribers' do
+ it 'returns [] when no subcribers exists' do
+ expect(resource.subscribers).to be_empty
+ end
+
+ it 'returns the subscribed users' do
+ resource.subscriptions.create(user: user, subscribed: true)
+ resource.subscriptions.create(user: create(:user), subscribed: false)
+
+ expect(resource.subscribers).to eq [user]
+ end
+ end
+
+ describe '#toggle_subscription' do
+ it 'toggles the current subscription state for the given user' do
+ expect(resource.subscribed?(user)).to be_falsey
+
+ resource.toggle_subscription(user)
+
+ expect(resource.subscribed?(user)).to be_truthy
+ end
+ end
+
+ describe '#unsubscribe' do
+ it 'unsubscribes the given current user' do
+ resource.subscriptions.create(user: user, subscribed: true)
+ expect(resource.subscribed?(user)).to be_truthy
+
+ resource.unsubscribe(user)
+
+ expect(resource.subscribed?(user)).to be_falsey
+ end
+ end
+end
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index ec2a923f91b..5fe44246738 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -65,6 +65,42 @@ describe Event, models: true do
it { expect(@event.author).to eq(@user) }
end
+ describe '#proper?' do
+ context 'issue event' do
+ let(:project) { create(:empty_project, :public) }
+ let(:non_member) { create(:user) }
+ let(:member) { create(:user) }
+ let(:author) { create(:author) }
+ let(:assignee) { create(:user) }
+ let(:admin) { create(:admin) }
+ let(:event) { Event.new(project: project, action: Event::CREATED, target: issue, author_id: author.id) }
+
+ before do
+ project.team << [member, :developer]
+ end
+
+ context 'for non confidential issues' do
+ let(:issue) { create(:issue, project: project, author: author, assignee: assignee) }
+
+ it { expect(event.proper?(non_member)).to eq true }
+ it { expect(event.proper?(author)).to eq true }
+ it { expect(event.proper?(assignee)).to eq true }
+ it { expect(event.proper?(member)).to eq true }
+ it { expect(event.proper?(admin)).to eq true }
+ end
+
+ context 'for confidential issues' do
+ let(:issue) { create(:issue, :confidential, project: project, author: author, assignee: assignee) }
+
+ it { expect(event.proper?(non_member)).to eq false }
+ it { expect(event.proper?(author)).to eq true }
+ it { expect(event.proper?(assignee)).to eq true }
+ it { expect(event.proper?(member)).to eq true }
+ it { expect(event.proper?(admin)).to eq true }
+ end
+ end
+ end
+
describe '.limit_recent' do
let!(:event1) { create(:closed_issue_event) }
let!(:event2) { create(:closed_issue_event) }
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 3c995053eec..c9245fc9535 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -103,4 +103,30 @@ describe Group, models: true do
expect(group.avatar_type).to eq(["only images allowed"])
end
end
+
+ describe '.search' do
+ it 'returns groups with a matching name' do
+ expect(described_class.search(group.name)).to eq([group])
+ end
+
+ it 'returns groups with a partially matching name' do
+ expect(described_class.search(group.name[0..2])).to eq([group])
+ end
+
+ it 'returns groups with a matching name regardless of the casing' do
+ expect(described_class.search(group.name.upcase)).to eq([group])
+ end
+
+ it 'returns groups with a matching path' do
+ expect(described_class.search(group.path)).to eq([group])
+ end
+
+ it 'returns groups with a partially matching path' do
+ expect(described_class.search(group.path[0..2])).to eq([group])
+ end
+
+ it 'returns groups with a matching path regardless of the casing' do
+ expect(described_class.search(group.path.upcase)).to eq([group])
+ end
+ end
end
diff --git a/spec/models/hooks/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb
index 1455661485b..f800f415bd2 100644
--- a/spec/models/hooks/service_hook_spec.rb
+++ b/spec/models/hooks/service_hook_spec.rb
@@ -31,7 +31,7 @@ describe ServiceHook, models: true do
WebMock.stub_request(:post, @service_hook.url)
end
- it "POSTs to the web hook URL" do
+ it "POSTs to the webhook URL" do
@service_hook.execute(@data)
expect(WebMock).to have_requested(:post, @service_hook.url).with(
headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'Service Hook' }
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index 6ea99952a8f..04bc2dcfb16 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -52,7 +52,7 @@ describe WebHook, models: true do
WebMock.stub_request(:post, @project_hook.url)
end
- it "POSTs to the web hook URL" do
+ it "POSTs to the webhook URL" do
@project_hook.execute(@data, 'push_hooks')
expect(WebMock).to have_requested(:post, @project_hook.url).with(
headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'Push Hook' }
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 52271c7c8c6..8334545b759 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -105,6 +105,90 @@ describe Issue, models: true do
end
end
+ describe '#referenced_merge_requests' do
+ it 'returns the referenced merge requests' do
+ project = create(:project, :public)
+
+ mr1 = create(:merge_request,
+ source_project: project,
+ source_branch: 'master',
+ target_branch: 'feature')
+
+ mr2 = create(:merge_request,
+ source_project: project,
+ source_branch: 'feature',
+ target_branch: 'master')
+
+ issue = create(:issue, description: mr1.to_reference, project: project)
+
+ create(:note_on_issue,
+ noteable: issue,
+ note: mr2.to_reference,
+ project_id: project.id)
+
+ expect(issue.referenced_merge_requests).to eq([mr1, mr2])
+ end
+ end
+
+ describe '#can_move?' do
+ let(:user) { create(:user) }
+ let(:issue) { create(:issue) }
+ subject { issue.can_move?(user) }
+
+ context 'user is not a member of project issue belongs to' do
+ it { is_expected.to eq false}
+ end
+
+ context 'user is reporter in project issue belongs to' do
+ let(:project) { create(:project) }
+ let(:issue) { create(:issue, project: project) }
+
+ before { project.team << [user, :reporter] }
+
+ it { is_expected.to eq true }
+
+ context 'checking destination project also' do
+ subject { issue.can_move?(user, to_project) }
+ let(:to_project) { create(:project) }
+
+ context 'destination project allowed' do
+ before { to_project.team << [user, :reporter] }
+ it { is_expected.to eq true }
+ end
+
+ context 'destination project not allowed' do
+ before { to_project.team << [user, :guest] }
+ it { is_expected.to eq false }
+ end
+ end
+ end
+ end
+
+ describe '#moved?' do
+ let(:issue) { create(:issue) }
+ subject { issue.moved? }
+
+ context 'issue not moved' do
+ it { is_expected.to eq false }
+ end
+
+ context 'issue already moved' do
+ let(:moved_to_issue) { create(:issue) }
+ let(:issue) { create(:issue, moved_to: moved_to_issue) }
+
+ it { is_expected.to eq true }
+ end
+ end
+
+ describe '#related_branches' do
+ it "selects the right branches" do
+ allow(subject.project.repository).to receive(:branch_names).
+ and_return(["mpempe", "#{subject.iid}mepmep", subject.to_branch_name])
+
+ expect(subject.related_branches).to eq([subject.to_branch_name])
+ end
+ end
+
it_behaves_like 'an editable mentionable' do
subject { create(:issue) }
@@ -115,4 +199,12 @@ describe Issue, models: true do
it_behaves_like 'a Taskable' do
let(:subject) { create :issue }
end
+
+ describe "#to_branch_name" do
+ let(:issue) { create(:issue, title: 'a' * 30) }
+
+ it "starts with the issue iid" do
+ expect(issue.to_branch_name).to match /-#{issue.iid}\z/
+ end
+ end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 59c40922abb..f2f07e4ee17 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -80,6 +80,47 @@ describe MergeRequest, models: true do
it { is_expected.to respond_to(:merge_when_build_succeeds) }
end
+ describe '.in_projects' do
+ it 'returns the merge requests for a set of projects' do
+ expect(described_class.in_projects(Project.all)).to eq([subject])
+ end
+ end
+
+ describe '#target_sha' do
+ context 'when the target branch does not exist anymore' do
+ subject { create(:merge_request).tap { |mr| mr.update_attribute(:target_branch, 'deleted') } }
+
+ it 'returns nil' do
+ expect(subject.target_sha).to be_nil
+ end
+ end
+ end
+
+ describe '#source_sha' do
+ let(:last_branch_commit) { subject.source_project.repository.commit(subject.source_branch) }
+
+ context 'with diffs' do
+ subject { create(:merge_request, :with_diffs) }
+ it 'returns the sha of the source branch last commit' do
+ expect(subject.source_sha).to eq(last_branch_commit.sha)
+ end
+ end
+
+ context 'without diffs' do
+ subject { create(:merge_request, :without_diffs) }
+ it 'returns the sha of the source branch last commit' do
+ expect(subject.source_sha).to eq(last_branch_commit.sha)
+ end
+ end
+
+ context 'when the merge request is being created' do
+ subject { build(:merge_request, source_branch: nil, compare_commits: []) }
+ it 'returns nil' do
+ expect(subject.source_sha).to be_nil
+ end
+ end
+ end
+
describe '#to_reference' do
it 'returns a String reference to the object' do
expect(subject.to_reference).to eq "!#{subject.iid}"
@@ -144,6 +185,7 @@ describe MergeRequest, models: true do
let(:commit2) { double('commit2', safe_message: "Fixes #{issue1.to_reference}") }
before do
+ subject.project.team << [subject.author, :developer]
allow(subject).to receive(:commits).and_return([commit0, commit1, commit2])
end
@@ -174,24 +216,11 @@ describe MergeRequest, models: true do
end
describe "#work_in_progress?" do
- it "detects the 'WIP ' prefix" do
- subject.title = "WIP #{subject.title}"
- expect(subject).to be_work_in_progress
- end
-
- it "detects the 'WIP: ' prefix" do
- subject.title = "WIP: #{subject.title}"
- expect(subject).to be_work_in_progress
- end
-
- it "detects the '[WIP] ' prefix" do
- subject.title = "[WIP] #{subject.title}"
- expect(subject).to be_work_in_progress
- end
-
- it "detects the '[WIP]' prefix" do
- subject.title = "[WIP]#{subject.title}"
- expect(subject).to be_work_in_progress
+ ['WIP ', 'WIP:', 'WIP: ', '[WIP]', '[WIP] ', ' [WIP] WIP [WIP] WIP: WIP '].each do |wip_prefix|
+ it "detects the '#{wip_prefix}' prefix" do
+ subject.title = "#{wip_prefix}#{subject.title}"
+ expect(subject).to be_work_in_progress
+ end
end
it "doesn't detect WIP for words starting with WIP" do
@@ -199,6 +228,11 @@ describe MergeRequest, models: true do
expect(subject).not_to be_work_in_progress
end
+ it "doesn't detect WIP for words containing with WIP" do
+ subject.title = "WupWipwap #{subject.title}"
+ expect(subject).not_to be_work_in_progress
+ end
+
it "doesn't detect WIP by default" do
expect(subject).not_to be_work_in_progress
end
@@ -278,6 +312,18 @@ describe MergeRequest, models: true do
let(:project) { create(:project) }
let(:fork_project) { create(:project, forked_from_project: project) }
+ context 'when the target branch does not exist anymore' do
+ subject { create(:merge_request).tap { |mr| mr.update_attribute(:target_branch, 'deleted') } }
+
+ it 'does not crash' do
+ expect{ subject.diverged_commits_count }.not_to raise_error
+ end
+
+ it 'returns 0' do
+ expect(subject.diverged_commits_count).to eq(0)
+ end
+ end
+
context 'diverged on same repository' do
subject(:merge_request_with_divergence) { create(:merge_request, :diverged, source_project: project, target_project: project) }
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 1b1380ce4e2..72a4ea70228 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -32,6 +32,7 @@ describe Milestone, models: true do
let(:milestone) { create(:milestone) }
let(:issue) { create(:issue) }
+ let(:user) { create(:user) }
describe "unique milestone title per project" do
it "shouldn't accept the same title in a project twice" do
@@ -50,18 +51,17 @@ describe Milestone, models: true do
describe "#percent_complete" do
it "should not count open issues" do
milestone.issues << issue
- expect(milestone.percent_complete).to eq(0)
+ expect(milestone.percent_complete(user)).to eq(0)
end
it "should count closed issues" do
issue.close
milestone.issues << issue
- expect(milestone.percent_complete).to eq(100)
+ expect(milestone.percent_complete(user)).to eq(100)
end
it "should recover from dividing by zero" do
- expect(milestone.issues).to receive(:count).and_return(0)
- expect(milestone.percent_complete).to eq(0)
+ expect(milestone.percent_complete(user)).to eq(0)
end
end
@@ -103,7 +103,7 @@ describe Milestone, models: true do
)
end
- it { expect(milestone.percent_complete).to eq(75) }
+ it { expect(milestone.percent_complete(user)).to eq(75) }
end
describe :items_count do
@@ -113,24 +113,23 @@ describe Milestone, models: true do
milestone.merge_requests << create(:merge_request)
end
- it { expect(milestone.closed_items_count).to eq(1) }
- it { expect(milestone.open_items_count).to eq(2) }
- it { expect(milestone.total_items_count).to eq(3) }
- it { expect(milestone.is_empty?).to be_falsey }
+ it { expect(milestone.closed_items_count(user)).to eq(1) }
+ it { expect(milestone.total_items_count(user)).to eq(3) }
+ it { expect(milestone.is_empty?(user)).to be_falsey }
end
describe :can_be_closed? do
it { expect(milestone.can_be_closed?).to be_truthy }
end
- describe :is_empty? do
+ describe :total_items_count do
before do
create :closed_issue, milestone: milestone
create :merge_request, milestone: milestone
end
it 'Should return total count of issues and merge requests assigned to milestone' do
- expect(milestone.total_items_count).to eq 2
+ expect(milestone.total_items_count(user)).to eq 2
end
end
@@ -182,4 +181,34 @@ describe Milestone, models: true do
expect(issue4.position).to eq(42)
end
end
+
+ describe '.search' do
+ let(:milestone) { create(:milestone, title: 'foo', description: 'bar') }
+
+ it 'returns milestones with a matching title' do
+ expect(described_class.search(milestone.title)).to eq([milestone])
+ end
+
+ it 'returns milestones with a partially matching title' do
+ expect(described_class.search(milestone.title[0..2])).to eq([milestone])
+ end
+
+ it 'returns milestones with a matching title regardless of the casing' do
+ expect(described_class.search(milestone.title.upcase)).to eq([milestone])
+ end
+
+ it 'returns milestones with a matching description' do
+ expect(described_class.search(milestone.description)).to eq([milestone])
+ end
+
+ it 'returns milestones with a partially matching description' do
+ expect(described_class.search(milestone.description[0..2])).
+ to eq([milestone])
+ end
+
+ it 'returns milestones with a matching description regardless of the casing' do
+ expect(described_class.search(milestone.description.upcase)).
+ to eq([milestone])
+ end
+ end
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index e0b3290e416..3c3a580942a 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -41,13 +41,32 @@ describe Namespace, models: true do
it { expect(namespace.human_name).to eq(namespace.owner_name) }
end
- describe :search do
- before do
- @namespace = create :namespace
+ describe '.search' do
+ let(:namespace) { create(:namespace) }
+
+ it 'returns namespaces with a matching name' do
+ expect(described_class.search(namespace.name)).to eq([namespace])
+ end
+
+ it 'returns namespaces with a partially matching name' do
+ expect(described_class.search(namespace.name[0..2])).to eq([namespace])
+ end
+
+ it 'returns namespaces with a matching name regardless of the casing' do
+ expect(described_class.search(namespace.name.upcase)).to eq([namespace])
+ end
+
+ it 'returns namespaces with a matching path' do
+ expect(described_class.search(namespace.path)).to eq([namespace])
end
- it { expect(Namespace.search(@namespace.path)).to eq([@namespace]) }
- it { expect(Namespace.search('unknown')).to eq([]) }
+ it 'returns namespaces with a partially matching path' do
+ expect(described_class.search(namespace.path[0..2])).to eq([namespace])
+ end
+
+ it 'returns namespaces with a matching path regardless of the casing' do
+ expect(described_class.search(namespace.path.upcase)).to eq([namespace])
+ end
end
describe :move_dir do
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 583937ca748..6b18936edb1 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -24,7 +24,7 @@ require 'spec_helper'
describe Note, models: true do
describe 'associations' do
it { is_expected.to belong_to(:project) }
- it { is_expected.to belong_to(:noteable) }
+ it { is_expected.to belong_to(:noteable).touch(true) }
it { is_expected.to belong_to(:author).class_name('User') }
it { is_expected.to have_many(:todos).dependent(:destroy) }
@@ -140,13 +140,19 @@ describe Note, models: true do
end
end
- describe :search do
- let!(:note) { create(:note, note: "WoW") }
+ describe '.search' do
+ let(:note) { create(:note, note: 'WoW') }
- it { expect(Note.search('wow')).to include(note) }
+ it 'returns notes with matching content' do
+ expect(described_class.search(note.note)).to eq([note])
+ end
+
+ it 'returns notes with matching content regardless of the casing' do
+ expect(described_class.search('WOW')).to eq([note])
+ end
end
- describe :grouped_awards do
+ describe '.grouped_awards' do
before do
create :note, note: "smile", is_award: true
create :note, note: "smile", is_award: true
@@ -163,6 +169,66 @@ describe Note, models: true do
end
end
+ describe '#active?' do
+ it 'is always true when the note has no associated diff' do
+ note = build(:note)
+
+ expect(note).to receive(:diff).and_return(nil)
+
+ expect(note).to be_active
+ end
+
+ it 'is never true when the note has no noteable associated' do
+ note = build(:note)
+
+ expect(note).to receive(:diff).and_return(double)
+ expect(note).to receive(:noteable).and_return(nil)
+
+ expect(note).not_to be_active
+ end
+
+ it 'returns the memoized value if defined' do
+ note = build(:note)
+
+ expect(note).to receive(:diff).and_return(double)
+ expect(note).to receive(:noteable).and_return(double)
+
+ note.instance_variable_set(:@active, 'foo')
+ expect(note).not_to receive(:find_noteable_diff)
+
+ expect(note.active?).to eq 'foo'
+ end
+
+ context 'for a merge request noteable' do
+ it 'is false when noteable has no matching diff' do
+ merge = build_stubbed(:merge_request, :simple)
+ note = build(:note, noteable: merge)
+
+ allow(note).to receive(:diff).and_return(double)
+ expect(note).to receive(:find_noteable_diff).and_return(nil)
+
+ expect(note).not_to be_active
+ end
+
+ it 'is true when noteable has a matching diff' do
+ merge = create(:merge_request, :simple)
+
+ # Generate a real line_code value so we know it will match. We use a
+ # random line from a random diff just for funsies.
+ diff = merge.diffs.to_a.sample
+ line = Gitlab::Diff::Parser.new.parse(diff.diff.each_line).to_a.sample
+ code = Gitlab::Diff::LineCode.generate(diff.new_path, line.new_pos, line.old_pos)
+
+ # We're persisting in order to trigger the set_diff callback
+ note = create(:note, noteable: merge, line_code: code)
+
+ # Make sure we don't get a false positive from a guard clause
+ expect(note).to receive(:find_noteable_diff).and_call_original
+ expect(note).to be_active
+ end
+ end
+ end
+
describe "editable?" do
it "returns true" do
note = build(:note)
@@ -220,4 +286,12 @@ describe Note, models: true do
expect(note.is_award?).to be_falsy
end
end
+
+ describe 'clear_blank_line_code!' do
+ it 'clears a blank line code before validation' do
+ note = build(:note, line_code: ' ')
+
+ expect { note.valid? }.to change(note, :line_code).to(nil)
+ end
+ end
end
diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb
new file mode 100644
index 00000000000..2fa6715fcaf
--- /dev/null
+++ b/spec/models/project_group_link_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe ProjectGroupLink do
+ describe "Associations" do
+ it { should belong_to(:group) }
+ it { should belong_to(:project) }
+ end
+
+ describe "Validation" do
+ let!(:project_group_link) { create(:project_group_link) }
+
+ it { should validate_presence_of(:project_id) }
+ it { should validate_uniqueness_of(:group_id).scoped_to(:project_id).with_message(/already shared/) }
+ it { should validate_presence_of(:group_id) }
+ it { should validate_presence_of(:group_access) }
+ end
+end
diff --git a/spec/models/project_snippet_spec.rb b/spec/models/project_snippet_spec.rb
index cc92eb0bd9f..e0feb606f78 100644
--- a/spec/models/project_snippet_spec.rb
+++ b/spec/models/project_snippet_spec.rb
@@ -10,7 +10,6 @@
# created_at :datetime
# updated_at :datetime
# file_name :string(255)
-# expires_at :datetime
# type :string(255)
# visibility_level :integer default(0), not null
#
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index f9842d23afa..624022c1dda 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -68,6 +68,7 @@ describe Project, models: true do
it { is_expected.to have_many(:runners) }
it { is_expected.to have_many(:variables) }
it { is_expected.to have_many(:triggers) }
+ it { is_expected.to have_many(:todos).dependent(:destroy) }
end
describe 'modules' do
@@ -561,7 +562,7 @@ describe Project, models: true do
end
describe '#visibility_level_allowed?' do
- let(:project) { create :project, visibility_level: Gitlab::VisibilityLevel::INTERNAL }
+ let(:project) { create(:project, :internal) }
context 'when checking on non-forked project' do
it { expect(project.visibility_level_allowed?(Gitlab::VisibilityLevel::PRIVATE)).to be_truthy }
@@ -581,7 +582,64 @@ describe Project, models: true do
it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::INTERNAL)).to be_truthy }
it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::PUBLIC)).to be_falsey }
end
+ end
+
+ describe '.search' do
+ let(:project) { create(:project, description: 'kitten mittens') }
+
+ it 'returns projects with a matching name' do
+ expect(described_class.search(project.name)).to eq([project])
+ end
+
+ it 'returns projects with a partially matching name' do
+ expect(described_class.search(project.name[0..2])).to eq([project])
+ end
+
+ it 'returns projects with a matching name regardless of the casing' do
+ expect(described_class.search(project.name.upcase)).to eq([project])
+ end
+
+ it 'returns projects with a matching description' do
+ expect(described_class.search(project.description)).to eq([project])
+ end
+
+ it 'returns projects with a partially matching description' do
+ expect(described_class.search('kitten')).to eq([project])
+ end
+
+ it 'returns projects with a matching description regardless of the casing' do
+ expect(described_class.search('KITTEN')).to eq([project])
+ end
+ it 'returns projects with a matching path' do
+ expect(described_class.search(project.path)).to eq([project])
+ end
+
+ it 'returns projects with a partially matching path' do
+ expect(described_class.search(project.path[0..2])).to eq([project])
+ end
+
+ it 'returns projects with a matching path regardless of the casing' do
+ expect(described_class.search(project.path.upcase)).to eq([project])
+ end
+
+ it 'returns projects with a matching namespace name' do
+ expect(described_class.search(project.namespace.name)).to eq([project])
+ end
+
+ it 'returns projects with a partially matching namespace name' do
+ expect(described_class.search(project.namespace.name[0..2])).to eq([project])
+ end
+
+ it 'returns projects with a matching namespace name regardless of the casing' do
+ expect(described_class.search(project.namespace.name.upcase)).to eq([project])
+ end
+
+ it 'returns projects when eager loading namespaces' do
+ relation = described_class.all.includes(:namespace)
+
+ expect(relation.search(project.namespace.name)).to eq([project])
+ end
end
describe '#rename_repo' do
@@ -646,4 +704,61 @@ describe Project, models: true do
project.expire_caches_before_rename('foo')
end
end
+
+ describe '.search_by_title' do
+ let(:project) { create(:project, name: 'kittens') }
+
+ it 'returns projects with a matching name' do
+ expect(described_class.search_by_title(project.name)).to eq([project])
+ end
+
+ it 'returns projects with a partially matching name' do
+ expect(described_class.search_by_title('kitten')).to eq([project])
+ end
+
+ it 'returns projects with a matching name regardless of the casing' do
+ expect(described_class.search_by_title('KITTENS')).to eq([project])
+ end
+ end
+
+ describe '#create_repository' do
+ let(:project) { create(:project) }
+ let(:shell) { Gitlab::Shell.new }
+
+ before do
+ allow(project).to receive(:gitlab_shell).and_return(shell)
+ end
+
+ context 'using a regular repository' do
+ it 'creates the repository' do
+ expect(shell).to receive(:add_repository).
+ with(project.path_with_namespace).
+ and_return(true)
+
+ expect(project.repository).to receive(:after_create)
+
+ expect(project.create_repository).to eq(true)
+ end
+
+ it 'adds an error if the repository could not be created' do
+ expect(shell).to receive(:add_repository).
+ with(project.path_with_namespace).
+ and_return(false)
+
+ expect(project.repository).not_to receive(:after_create)
+
+ expect(project.create_repository).to eq(false)
+ expect(project.errors).not_to be_empty
+ end
+ end
+
+ context 'using a forked repository' do
+ it 'does nothing' do
+ expect(project).to receive(:forked?).and_return(true)
+ expect(shell).not_to receive(:add_repository)
+
+ project.create_repository
+ end
+ end
+ end
end
diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb
index 7b63da005f0..bacb17a8883 100644
--- a/spec/models/project_team_spec.rb
+++ b/spec/models/project_team_spec.rb
@@ -67,6 +67,50 @@ describe ProjectTeam, models: true do
end
end
+ describe :max_invited_level do
+ let(:group) { create(:group) }
+ let(:project) { create(:empty_project) }
+
+ before do
+ project.project_group_links.create(
+ group: group,
+ group_access: Gitlab::Access::DEVELOPER
+ )
+
+ group.add_user(master, Gitlab::Access::MASTER)
+ group.add_user(reporter, Gitlab::Access::REPORTER)
+ end
+
+ it { expect(project.team.max_invited_level(master.id)).to eq(Gitlab::Access::DEVELOPER) }
+ it { expect(project.team.max_invited_level(reporter.id)).to eq(Gitlab::Access::REPORTER) }
+ it { expect(project.team.max_invited_level(nonmember.id)).to be_nil }
+ end
+
+ describe :max_member_access do
+ let(:group) { create(:group) }
+ let(:project) { create(:empty_project) }
+
+ before do
+ project.project_group_links.create(
+ group: group,
+ group_access: Gitlab::Access::DEVELOPER
+ )
+
+ group.add_user(master, Gitlab::Access::MASTER)
+ group.add_user(reporter, Gitlab::Access::REPORTER)
+ end
+
+ it { expect(project.team.max_member_access(master.id)).to eq(Gitlab::Access::DEVELOPER) }
+ it { expect(project.team.max_member_access(reporter.id)).to eq(Gitlab::Access::REPORTER) }
+ it { expect(project.team.max_member_access(nonmember.id)).to be_nil }
+
+ it "does not have an access" do
+ project.namespace.update(share_with_group_lock: true)
+ expect(project.team.max_member_access(master.id)).to be_nil
+ expect(project.team.max_member_access(reporter.id)).to be_nil
+ end
+ end
+
describe "#human_max_access" do
it 'returns Master role' do
user = create(:user)
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index a2085df5bcd..532e3f013fd 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -244,6 +244,18 @@ describe ProjectWiki, models: true do
end
end
+ describe '#create_repo!' do
+ it 'creates a repository' do
+ expect(subject).to receive(:init_repo).
+ with(subject.path_with_namespace).
+ and_return(true)
+
+ expect(subject.repository).to receive(:after_create)
+
+ expect(subject.create_repo!).to be_an_instance_of(Gollum::Wiki)
+ end
+ end
+
private
def create_temp_repo(path)
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 1c7d66398cb..7eac70ae948 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -101,13 +101,29 @@ describe Repository, models: true do
end
describe 'parsing result' do
- subject { repository.parse_search_result(results.first) }
+ subject { repository.parse_search_result(search_result) }
+ let(:search_result) { results.first }
it { is_expected.to be_an OpenStruct }
it { expect(subject.filename).to eq('CHANGELOG') }
+ it { expect(subject.basename).to eq('CHANGELOG') }
it { expect(subject.ref).to eq('master') }
it { expect(subject.startline).to eq(186) }
it { expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n") }
+
+ context "when filename has extension" do
+ let(:search_result) { "master:CONTRIBUTE.md:5:- [Contribute to GitLab](#contribute-to-gitlab)\n" }
+
+ it { expect(subject.filename).to eq('CONTRIBUTE.md') }
+ it { expect(subject.basename).to eq('CONTRIBUTE') }
+ end
+
+ context "when file under directory" do
+ let(:search_result) { "master:a/b/c.md:5:a b c\n" }
+
+ it { expect(subject.filename).to eq('a/b/c.md') }
+ it { expect(subject.basename).to eq('a/b/c') }
+ end
end
end
@@ -148,6 +164,12 @@ describe Repository, models: true do
expect(branch.name).to eq('new_feature')
end
+
+ it 'calls the after_create_branch hook' do
+ expect(repository).to receive(:after_create_branch)
+
+ repository.add_branch(user, 'new_feature', 'master')
+ end
end
context 'when pre hooks failed' do
@@ -405,7 +427,7 @@ describe Repository, models: true do
end
end
- describe '#expire_branch_ache' do
+ describe '#expire_branch_cache' do
# This method is private but we need it for testing purposes. Sadly there's
# no other proper way of testing caching operations.
let(:cache) { repository.send(:cache) }
@@ -515,6 +537,12 @@ describe Repository, models: true do
repository.before_delete
end
+
+ it 'flushes the exists cache' do
+ expect(repository).to receive(:expire_exists_cache)
+
+ repository.before_delete
+ end
end
describe 'when a repository exists' do
@@ -556,11 +584,12 @@ describe Repository, models: true do
end
end
- describe '#before_create_tag' do
+ describe '#before_push_tag' do
it 'flushes the cache' do
expect(repository).to receive(:expire_cache)
+ expect(repository).to receive(:expire_tag_count_cache)
- repository.before_create_tag
+ repository.before_push_tag
end
end
@@ -570,13 +599,19 @@ describe Repository, models: true do
repository.after_import
end
+
+ it 'flushes the exists cache' do
+ expect(repository).to receive(:expire_exists_cache)
+
+ repository.after_import
+ end
end
describe '#after_push_commit' do
it 'flushes the cache' do
- expect(repository).to receive(:expire_cache).with('master')
+ expect(repository).to receive(:expire_cache).with('master', '123')
- repository.after_push_commit('master')
+ repository.after_push_commit('master', '123')
end
end
@@ -595,4 +630,214 @@ describe Repository, models: true do
repository.after_remove_branch
end
end
+
+ describe '#after_create' do
+ it 'flushes the exists cache' do
+ expect(repository).to receive(:expire_exists_cache)
+
+ repository.after_create
+ end
+ end
+
+ describe "#main_language" do
+ it 'shows the main language of the project' do
+ expect(repository.main_language).to eq("Ruby")
+ end
+
+ it 'returns nil when the repository is empty' do
+ allow(repository).to receive(:empty?).and_return(true)
+
+ expect(repository.main_language).to be_nil
+ end
+ end
+
+ describe '#before_remove_tag' do
+ it 'flushes the tag cache' do
+ expect(repository).to receive(:expire_tag_count_cache)
+
+ repository.before_remove_tag
+ end
+ end
+
+ describe '#branch_count' do
+ it 'returns the number of branches' do
+ expect(repository.branch_count).to be_an_instance_of(Fixnum)
+ end
+ end
+
+ describe '#tag_count' do
+ it 'returns the number of tags' do
+ expect(repository.tag_count).to be_an_instance_of(Fixnum)
+ end
+ end
+
+ describe '#expire_branch_count_cache' do
+ let(:cache) { repository.send(:cache) }
+
+ it 'expires the cache' do
+ expect(cache).to receive(:expire).with(:branch_count)
+
+ repository.expire_branch_count_cache
+ end
+ end
+
+ describe '#expire_tag_count_cache' do
+ let(:cache) { repository.send(:cache) }
+
+ it 'expires the cache' do
+ expect(cache).to receive(:expire).with(:tag_count)
+
+ repository.expire_tag_count_cache
+ end
+ end
+
+ describe '#add_tag' do
+ it 'adds a tag' do
+ expect(repository).to receive(:before_push_tag)
+
+ expect_any_instance_of(Gitlab::Shell).to receive(:add_tag).
+ with(repository.path_with_namespace, '8.5', 'master', 'foo')
+
+ repository.add_tag('8.5', 'master', 'foo')
+ end
+ end
+
+ describe '#rm_branch' do
+ let(:user) { create(:user) }
+
+ it 'removes a branch' do
+ expect(repository).to receive(:before_remove_branch)
+ expect(repository).to receive(:after_remove_branch)
+
+ repository.rm_branch(user, 'feature')
+ end
+ end
+
+ describe '#rm_tag' do
+ it 'removes a tag' do
+ expect(repository).to receive(:before_remove_tag)
+
+ expect_any_instance_of(Gitlab::Shell).to receive(:rm_tag).
+ with(repository.path_with_namespace, '8.5')
+
+ repository.rm_tag('8.5')
+ end
+ end
+
+ describe '#avatar' do
+ it 'returns the first avatar file found in the repository' do
+ expect(repository).to receive(:blob_at_branch).
+ with('master', 'logo.png').
+ and_return(true)
+
+ expect(repository.avatar).to eq('logo.png')
+ end
+
+ it 'caches the output' do
+ allow(repository).to receive(:blob_at_branch).
+ with('master', 'logo.png').
+ and_return(true)
+
+ expect(repository.avatar).to eq('logo.png')
+
+ expect(repository).to_not receive(:blob_at_branch)
+ expect(repository.avatar).to eq('logo.png')
+ end
+ end
+
+ describe '#expire_avatar_cache' do
+ let(:cache) { repository.send(:cache) }
+
+ before do
+ allow(repository).to receive(:cache).and_return(cache)
+ end
+
+ context 'without a branch or revision' do
+ it 'flushes the cache' do
+ expect(cache).to receive(:expire).with(:avatar)
+
+ repository.expire_avatar_cache
+ end
+ end
+
+ context 'with a branch' do
+ it 'does not flush the cache if the branch is not the default branch' do
+ expect(cache).not_to receive(:expire)
+
+ repository.expire_avatar_cache('cats')
+ end
+
+ it 'flushes the cache if the branch equals the default branch' do
+ expect(cache).to receive(:expire).with(:avatar)
+
+ repository.expire_avatar_cache(repository.root_ref)
+ end
+ end
+
+ context 'with a branch and revision' do
+ let(:commit) { double(:commit) }
+
+ before do
+ allow(repository).to receive(:commit).and_return(commit)
+ end
+
+ it 'does not flush the cache if the commit does not change any logos' do
+ diff = double(:diff, new_path: 'test.txt')
+
+ expect(commit).to receive(:diffs).and_return([diff])
+ expect(cache).not_to receive(:expire)
+
+ repository.expire_avatar_cache(repository.root_ref, '123')
+ end
+
+ it 'flushes the cache if the commit changes any of the logos' do
+ diff = double(:diff, new_path: Repository::AVATAR_FILES[0])
+
+ expect(commit).to receive(:diffs).and_return([diff])
+ expect(cache).to receive(:expire).with(:avatar)
+
+ repository.expire_avatar_cache(repository.root_ref, '123')
+ end
+ end
+ end
+
+ describe '#expire_exists_cache' do
+ let(:cache) { repository.send(:cache) }
+
+ it 'expires the cache' do
+ expect(cache).to receive(:expire).with(:exists?)
+
+ repository.expire_exists_cache
+ end
+ end
+
+ describe '#build_cache' do
+ let(:cache) { repository.send(:cache) }
+
+ it 'builds the caches if they do not already exist' do
+ expect(cache).to receive(:exist?).
+ exactly(repository.cache_keys.length).
+ times.
+ and_return(false)
+
+ repository.cache_keys.each do |key|
+ expect(repository).to receive(key)
+ end
+
+ repository.build_cache
+ end
+
+ it 'does not build any caches that already exist' do
+ expect(cache).to receive(:exist?).
+ exactly(repository.cache_keys.length).
+ times.
+ and_return(true)
+
+ repository.cache_keys.each do |key|
+ expect(repository).to_not receive(key)
+ end
+
+ repository.build_cache
+ end
+ end
end
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index eb2dbbdc5a4..5077ac7b62b 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -10,7 +10,6 @@
# created_at :datetime
# updated_at :datetime
# file_name :string(255)
-# expires_at :datetime
# type :string(255)
# visibility_level :integer default(0), not null
#
@@ -60,4 +59,48 @@ describe Snippet, models: true do
expect(snippet.to_reference(cross)).to eq "#{project.to_reference}$#{snippet.id}"
end
end
+
+ describe '.search' do
+ let(:snippet) { create(:snippet) }
+
+ it 'returns snippets with a matching title' do
+ expect(described_class.search(snippet.title)).to eq([snippet])
+ end
+
+ it 'returns snippets with a partially matching title' do
+ expect(described_class.search(snippet.title[0..2])).to eq([snippet])
+ end
+
+ it 'returns snippets with a matching title regardless of the casing' do
+ expect(described_class.search(snippet.title.upcase)).to eq([snippet])
+ end
+
+ it 'returns snippets with a matching file name' do
+ expect(described_class.search(snippet.file_name)).to eq([snippet])
+ end
+
+ it 'returns snippets with a partially matching file name' do
+ expect(described_class.search(snippet.file_name[0..2])).to eq([snippet])
+ end
+
+ it 'returns snippets with a matching file name regardless of the casing' do
+ expect(described_class.search(snippet.file_name.upcase)).to eq([snippet])
+ end
+ end
+
+ describe '#search_code' do
+ let(:snippet) { create(:snippet, content: 'class Foo; end') }
+
+ it 'returns snippets with matching content' do
+ expect(described_class.search_code(snippet.content)).to eq([snippet])
+ end
+
+ it 'returns snippets with partially matching content' do
+ expect(described_class.search_code('class')).to eq([snippet])
+ end
+
+ it 'returns snippets with matching content regardless of the casing' do
+ expect(described_class.search_code('FOO')).to eq([snippet])
+ end
+ end
end
diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb
index fe9ea7e7d1e..d9b86b9368f 100644
--- a/spec/models/todo_spec.rb
+++ b/spec/models/todo_spec.rb
@@ -5,19 +5,24 @@
# id :integer not null, primary key
# user_id :integer not null
# project_id :integer not null
-# target_id :integer not null
+# target_id :integer
# target_type :string not null
# author_id :integer
-# note_id :integer
# action :integer not null
# state :string not null
# created_at :datetime
# updated_at :datetime
+# note_id :integer
+# commit_id :string
#
require 'spec_helper'
describe Todo, models: true do
+ let(:project) { create(:project) }
+ let(:commit) { project.commit }
+ let(:issue) { create(:issue) }
+
describe 'relationships' do
it { is_expected.to belong_to(:author).class_name("User") }
it { is_expected.to belong_to(:note) }
@@ -33,8 +38,22 @@ describe Todo, models: true do
describe 'validations' do
it { is_expected.to validate_presence_of(:action) }
- it { is_expected.to validate_presence_of(:target) }
+ it { is_expected.to validate_presence_of(:target_type) }
it { is_expected.to validate_presence_of(:user) }
+
+ context 'for commits' do
+ subject { described_class.new(target_type: 'Commit') }
+
+ it { is_expected.to validate_presence_of(:commit_id) }
+ it { is_expected.not_to validate_presence_of(:target_id) }
+ end
+
+ context 'for issuables' do
+ subject { described_class.new(target: issue) }
+
+ it { is_expected.to validate_presence_of(:target_id) }
+ it { is_expected.not_to validate_presence_of(:commit_id) }
+ end
end
describe '#body' do
@@ -55,15 +74,69 @@ describe Todo, models: true do
end
end
- describe '#done!' do
+ describe '#done' do
it 'changes state to done' do
todo = create(:todo, state: :pending)
- expect { todo.done! }.to change(todo, :state).from('pending').to('done')
+ expect { todo.done }.to change(todo, :state).from('pending').to('done')
end
it 'does not raise error when is already done' do
todo = create(:todo, state: :done)
- expect { todo.done! }.not_to raise_error
+ expect { todo.done }.not_to raise_error
+ end
+ end
+
+ describe '#for_commit?' do
+ it 'returns true when target is a commit' do
+ subject.target_type = 'Commit'
+ expect(subject.for_commit?).to eq true
+ end
+
+ it 'returns false when target is an issuable' do
+ subject.target_type = 'Issue'
+ expect(subject.for_commit?).to eq false
+ end
+ end
+
+ describe '#target' do
+ context 'for commits' do
+ it 'returns an instance of Commit when exists' do
+ subject.project = project
+ subject.target_type = 'Commit'
+ subject.commit_id = commit.id
+
+ expect(subject.target).to be_a(Commit)
+ expect(subject.target).to eq commit
+ end
+
+ it 'returns nil when does not exists' do
+ subject.project = project
+ subject.target_type = 'Commit'
+ subject.commit_id = 'xxxx'
+
+ expect(subject.target).to be_nil
+ end
+ end
+
+ it 'returns the issuable for issuables' do
+ subject.target_id = issue.id
+ subject.target_type = issue.class.name
+ expect(subject.target).to eq issue
+ end
+ end
+
+ describe '#target_reference' do
+ it 'returns the short commit id for commits' do
+ subject.project = project
+ subject.target_type = 'Commit'
+ subject.commit_id = commit.id
+
+ expect(subject.target_reference).to eq commit.short_id
+ end
+
+ it 'returns reference for issuables' do
+ subject.target = issue
+ expect(subject.target_reference).to eq issue.to_reference
end
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 412101ac9f9..0ab7fd88ce6 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -174,38 +174,26 @@ describe User, models: true do
end
end
end
-
- describe 'avatar' do
- it 'only validates when avatar is present and changed' do
- user = build(:user, :with_avatar)
-
- user.avatar_crop_x = nil
- user.avatar_crop_y = nil
- user.avatar_crop_size = nil
-
- expect(user).not_to be_valid
- expect(user.errors.keys).
- to match_array %i(avatar_crop_x avatar_crop_y avatar_crop_size)
- end
-
- it 'does not validate when avatar has not changed' do
- user = create(:user, :with_avatar)
-
- expect { user.avatar_crop_x = nil }.not_to change(user, :valid?)
- end
-
- it 'does not validate when avatar is not present' do
- user = create(:user)
-
- expect { user.avatar_crop_y = nil }.not_to change(user, :valid?)
- end
- end
end
describe "Respond to" do
it { is_expected.to respond_to(:is_admin?) }
it { is_expected.to respond_to(:name) }
it { is_expected.to respond_to(:private_token) }
+ it { is_expected.to respond_to(:external?) }
+ end
+
+ describe 'before save hook' do
+ context 'when saving an external user' do
+ let(:user) { create(:user) }
+ let(:external_user) { create(:user, external: true) }
+
+ it "sets other properties aswell" do
+ expect(external_user.can_create_team).to be_falsey
+ expect(external_user.can_create_group).to be_falsey
+ expect(external_user.projects_limit).to be 0
+ end
+ end
end
describe '#confirm' do
@@ -430,6 +418,7 @@ describe User, models: true do
expect(user.projects_limit).to eq(Gitlab.config.gitlab.default_projects_limit)
expect(user.can_create_group).to eq(Gitlab.config.gitlab.default_can_create_group)
expect(user.theme_id).to eq(Gitlab.config.gitlab.default_theme)
+ expect(user.external).to be_falsey
end
end
@@ -463,17 +452,43 @@ describe User, models: true do
end
end
- describe 'search' do
- let(:user1) { create(:user, username: 'James', email: 'james@testing.com') }
- let(:user2) { create(:user, username: 'jameson', email: 'jameson@example.com') }
+ describe '.search' do
+ let(:user) { create(:user) }
+
+ it 'returns users with a matching name' do
+ expect(described_class.search(user.name)).to eq([user])
+ end
+
+ it 'returns users with a partially matching name' do
+ expect(described_class.search(user.name[0..2])).to eq([user])
+ end
+
+ it 'returns users with a matching name regardless of the casing' do
+ expect(described_class.search(user.name.upcase)).to eq([user])
+ end
+
+ it 'returns users with a matching Email' do
+ expect(described_class.search(user.email)).to eq([user])
+ end
+
+ it 'returns users with a partially matching Email' do
+ expect(described_class.search(user.email[0..2])).to eq([user])
+ end
+
+ it 'returns users with a matching Email regardless of the casing' do
+ expect(described_class.search(user.email.upcase)).to eq([user])
+ end
+
+ it 'returns users with a matching username' do
+ expect(described_class.search(user.username)).to eq([user])
+ end
+
+ it 'returns users with a partially matching username' do
+ expect(described_class.search(user.username[0..2])).to eq([user])
+ end
- it "should be case insensitive" do
- expect(User.search(user1.username.upcase).to_a).to eq([user1])
- expect(User.search(user1.username.downcase).to_a).to eq([user1])
- expect(User.search(user2.username.upcase).to_a).to eq([user2])
- expect(User.search(user2.username.downcase).to_a).to eq([user2])
- expect(User.search(user1.username.downcase).to_a.size).to eq(2)
- expect(User.search(user2.username.downcase).to_a.size).to eq(1)
+ it 'returns users with a matching username regardless of the casing' do
+ expect(described_class.search(user.username.upcase)).to eq([user])
end
end
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 571ea2dae4c..bb2ab058003 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -3,7 +3,11 @@ require 'spec_helper'
describe API::API, api: true do
include ApiHelpers
let(:user) { create(:user) }
- let!(:project) { create(:project, namespace: user.namespace ) }
+ let(:non_member) { create(:user) }
+ let(:author) { create(:author) }
+ let(:assignee) { create(:assignee) }
+ let(:admin) { create(:admin) }
+ let!(:project) { create(:project, :public, namespace: user.namespace ) }
let!(:closed_issue) do
create :closed_issue,
author: user,
@@ -12,6 +16,13 @@ describe API::API, api: true do
state: :closed,
milestone: milestone
end
+ let!(:confidential_issue) do
+ create :issue,
+ :confidential,
+ project: project,
+ author: author,
+ assignee: assignee
+ end
let!(:issue) do
create :issue,
author: user,
@@ -123,10 +134,43 @@ describe API::API, api: true do
let(:base_url) { "/projects/#{project.id}" }
let(:title) { milestone.title }
- it "should return project issues" do
+ it 'should return project issues without confidential issues for non project members' do
+ get api("#{base_url}/issues", non_member)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(2)
+ expect(json_response.first['title']).to eq(issue.title)
+ end
+
+ it 'should return project confidential issues for author' do
+ get api("#{base_url}/issues", author)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
+ expect(json_response.first['title']).to eq(issue.title)
+ end
+
+ it 'should return project confidential issues for assignee' do
+ get api("#{base_url}/issues", assignee)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
+ expect(json_response.first['title']).to eq(issue.title)
+ end
+
+ it 'should return project issues with confidential issues for project members' do
get api("#{base_url}/issues", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
+ expect(json_response.first['title']).to eq(issue.title)
+ end
+
+ it 'should return project confidential issues for admin' do
+ get api("#{base_url}/issues", admin)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
expect(json_response.first['title']).to eq(issue.title)
end
@@ -206,6 +250,41 @@ describe API::API, api: true do
get api("/projects/#{project.id}/issues/54321", user)
expect(response.status).to eq(404)
end
+
+ context 'confidential issues' do
+ it "should return 404 for non project members" do
+ get api("/projects/#{project.id}/issues/#{confidential_issue.id}", non_member)
+ expect(response.status).to eq(404)
+ end
+
+ it "should return confidential issue for project members" do
+ get api("/projects/#{project.id}/issues/#{confidential_issue.id}", user)
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq(confidential_issue.title)
+ expect(json_response['iid']).to eq(confidential_issue.iid)
+ end
+
+ it "should return confidential issue for author" do
+ get api("/projects/#{project.id}/issues/#{confidential_issue.id}", author)
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq(confidential_issue.title)
+ expect(json_response['iid']).to eq(confidential_issue.iid)
+ end
+
+ it "should return confidential issue for assignee" do
+ get api("/projects/#{project.id}/issues/#{confidential_issue.id}", assignee)
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq(confidential_issue.title)
+ expect(json_response['iid']).to eq(confidential_issue.iid)
+ end
+
+ it "should return confidential issue for admin" do
+ get api("/projects/#{project.id}/issues/#{confidential_issue.id}", admin)
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq(confidential_issue.title)
+ expect(json_response['iid']).to eq(confidential_issue.iid)
+ end
+ end
end
describe "POST /projects/:id/issues" do
@@ -294,6 +373,35 @@ describe API::API, api: true do
expect(response.status).to eq(400)
expect(json_response['message']['labels']['?']['title']).to eq(['is invalid'])
end
+
+ context 'confidential issues' do
+ it "should return 403 for non project members" do
+ put api("/projects/#{project.id}/issues/#{confidential_issue.id}", non_member),
+ title: 'updated title'
+ expect(response.status).to eq(403)
+ end
+
+ it "should update a confidential issue for project members" do
+ put api("/projects/#{project.id}/issues/#{confidential_issue.id}", user),
+ title: 'updated title'
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq('updated title')
+ end
+
+ it "should update a confidential issue for author" do
+ put api("/projects/#{project.id}/issues/#{confidential_issue.id}", author),
+ title: 'updated title'
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq('updated title')
+ end
+
+ it "should update a confidential issue for admin" do
+ put api("/projects/#{project.id}/issues/#{confidential_issue.id}", admin),
+ title: 'updated title'
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq('updated title')
+ end
+ end
end
describe 'PUT /projects/:id/issues/:issue_id to update labels' do
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
new file mode 100644
index 00000000000..3722ddf5a33
--- /dev/null
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -0,0 +1,18 @@
+require 'rails_helper'
+
+describe API::API, api: true do
+ include ApiHelpers
+
+ describe 'GET /projects/:project_id/snippets/:id' do
+ # TODO (rspeicher): Deprecated; remove in 9.0
+ it 'always exposes expires_at as nil' do
+ admin = create(:admin)
+ snippet = create(:project_snippet, author: admin)
+
+ get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}", admin)
+
+ expect(json_response).to have_key('expires_at')
+ expect(json_response['expires_at']).to be_nil
+ end
+ end
+end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 9f2365a4832..a6699cdc81c 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -747,6 +747,42 @@ describe API::API, api: true do
end
end
+ describe "POST /projects/:id/share" do
+ let(:group) { create(:group) }
+
+ it "should share project with group" do
+ expect do
+ post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER
+ end.to change { ProjectGroupLink.count }.by(1)
+
+ expect(response.status).to eq 201
+ expect(json_response['group_id']).to eq group.id
+ expect(json_response['group_access']).to eq Gitlab::Access::DEVELOPER
+ end
+
+ it "should return a 400 error when group id is not given" do
+ post api("/projects/#{project.id}/share", user), group_access: Gitlab::Access::DEVELOPER
+ expect(response.status).to eq 400
+ end
+
+ it "should return a 400 error when access level is not given" do
+ post api("/projects/#{project.id}/share", user), group_id: group.id
+ expect(response.status).to eq 400
+ end
+
+ it "should return a 400 error when sharing is disabled" do
+ project.namespace.update(share_with_group_lock: true)
+ post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER
+ expect(response.status).to eq 400
+ end
+
+ it "should return a 409 error when wrong params passed" do
+ post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: 1234
+ expect(response.status).to eq 409
+ expect(json_response['message']).to eq 'Group access is not included in the list'
+ end
+ end
+
describe 'GET /projects/search/:query' do
let!(:query) { 'query'}
let!(:search) { create(:empty_project, name: query, creator_id: user.id, namespace: user.namespace) }
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index b82c5c7685f..679227bf881 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -47,6 +47,8 @@ describe API::API, api: true do
expect(json_response.first.keys).to include 'identities'
expect(json_response.first.keys).to include 'can_create_project'
expect(json_response.first.keys).to include 'two_factor_enabled'
+ expect(json_response.first.keys).to include 'last_sign_in_at'
+ expect(json_response.first.keys).to include 'confirmed_at'
end
end
end
@@ -118,6 +120,26 @@ describe API::API, api: true do
expect(response.status).to eq(201)
end
+ it 'creates non-external users by default' do
+ post api("/users", admin), attributes_for(:user)
+ expect(response.status).to eq(201)
+
+ user_id = json_response['id']
+ new_user = User.find(user_id)
+ expect(new_user).not_to eq nil
+ expect(new_user.external).to be_falsy
+ end
+
+ it 'should allow an external user to be created' do
+ post api("/users", admin), attributes_for(:user, external: true)
+ expect(response.status).to eq(201)
+
+ user_id = json_response['id']
+ new_user = User.find(user_id)
+ expect(new_user).not_to eq nil
+ expect(new_user.external).to be_truthy
+ end
+
it "should not create user with invalid email" do
post api('/users', admin),
email: 'invalid email',
@@ -260,6 +282,13 @@ describe API::API, api: true do
expect(user.reload.admin).to eq(true)
end
+ it "should update external status" do
+ put api("/users/#{user.id}", admin), { external: true }
+ expect(response.status).to eq 200
+ expect(json_response['external']).to eq(true)
+ expect(user.reload.external?).to be_truthy
+ end
+
it "should not update admin status" do
put api("/users/#{admin_user.id}", admin), { can_create_group: false }
expect(response.status).to eq(200)
diff --git a/spec/services/delete_tag_service_spec.rb b/spec/services/delete_tag_service_spec.rb
new file mode 100644
index 00000000000..5b7ba521812
--- /dev/null
+++ b/spec/services/delete_tag_service_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe DeleteTagService, services: true do
+ let(:project) { create(:project) }
+ let(:repository) { project.repository }
+ let(:user) { create(:user) }
+ let(:service) { described_class.new(project, user) }
+
+ let(:tag) { double(:tag, name: '8.5', target: 'abc123') }
+
+ describe '#execute' do
+ before do
+ allow(repository).to receive(:find_tag).and_return(tag)
+ end
+
+ it 'removes the tag' do
+ expect_any_instance_of(Gitlab::Shell).to receive(:rm_tag).
+ and_return(true)
+
+ expect(repository).to receive(:before_remove_tag)
+ expect(service).to receive(:success)
+
+ service.execute('8.5')
+ end
+ end
+end
diff --git a/spec/services/delete_user_service_spec.rb b/spec/services/delete_user_service_spec.rb
new file mode 100644
index 00000000000..a65938fa03b
--- /dev/null
+++ b/spec/services/delete_user_service_spec.rb
@@ -0,0 +1,58 @@
+require 'spec_helper'
+
+describe DeleteUserService, services: true do
+ describe "Deletes a user and all their personal projects" do
+ let!(:user) { create(:user) }
+ let!(:current_user) { create(:user) }
+ let!(:namespace) { create(:namespace, owner: user) }
+ let!(:project) { create(:project, namespace: namespace) }
+
+ context 'no options are given' do
+ it 'deletes the user' do
+ DeleteUserService.new(current_user).execute(user)
+
+ expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'will delete the project in the near future' do
+ expect_any_instance_of(Projects::DestroyService).to receive(:pending_delete!).once
+
+ DeleteUserService.new(current_user).execute(user)
+ end
+ end
+
+ context "solo owned groups present" do
+ let(:solo_owned) { create(:group) }
+ let(:member) { create(:group_member) }
+ let(:user) { member.user }
+
+ before do
+ solo_owned.group_members = [member]
+ DeleteUserService.new(current_user).execute(user)
+ end
+
+ it 'does not delete the user' do
+ expect(User.find(user.id)).to eq user
+ end
+ end
+
+ context "deletions with solo owned groups" do
+ let(:solo_owned) { create(:group) }
+ let(:member) { create(:group_member) }
+ let(:user) { member.user }
+
+ before do
+ solo_owned.group_members = [member]
+ DeleteUserService.new(current_user).execute(user, delete_solo_owned_groups: true)
+ end
+
+ it 'deletes solo owned groups' do
+ expect { Project.find(solo_owned.id) }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'deletes the user' do
+ expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+ end
+end
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index 994585fb32c..8490a729e51 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -29,7 +29,8 @@ describe GitPushService, services: true do
it { is_expected.to be_truthy }
it 'flushes general cached data' do
- expect(project.repository).to receive(:expire_cache).with('master')
+ expect(project.repository).to receive(:expire_cache).
+ with('master', newrev)
subject
end
@@ -46,7 +47,8 @@ describe GitPushService, services: true do
it { is_expected.to be_truthy }
it 'flushes general cached data' do
- expect(project.repository).to receive(:expire_cache).with('master')
+ expect(project.repository).to receive(:expire_cache).
+ with('master', newrev)
subject
end
@@ -65,7 +67,8 @@ describe GitPushService, services: true do
end
it 'flushes general cached data' do
- expect(project.repository).to receive(:expire_cache).with('master')
+ expect(project.repository).to receive(:expire_cache).
+ with('master', newrev)
subject
end
@@ -155,8 +158,25 @@ describe GitPushService, services: true do
end
end
- describe "Web Hooks" do
- context "execute web hooks" do
+ describe "Updates main language" do
+
+ context "before push" do
+ it { expect(project.main_language).to eq(nil) }
+ end
+
+ context "after push" do
+ before do
+ @service = execute_service(project, user, @oldrev, @newrev, @ref)
+ end
+
+ it { expect(@service.update_main_language).to eq(true) }
+ it { expect(project.main_language).to eq("Ruby") }
+ end
+ end
+
+
+ describe "Webhooks" do
+ context "execute webhooks" do
it "when pushing a branch for the first time" do
expect(project).to receive(:execute_hooks)
expect(project.default_branch).to eq("master")
@@ -195,12 +215,16 @@ describe GitPushService, services: true do
let(:commit) { project.commit }
before do
+ project.team << [commit_author, :developer]
+ project.team << [user, :developer]
+
allow(commit).to receive_messages(
safe_message: "this commit \n mentions #{issue.to_reference}",
references: [issue],
author_name: commit_author.name,
author_email: commit_author.email
)
+
allow(project.repository).to receive(:commits_between).and_return([commit])
end
@@ -254,22 +278,24 @@ describe GitPushService, services: true do
allow(project.repository).to receive(:commits_between).
and_return([closing_commit])
+
+ project.team << [commit_author, :master]
end
context "to default branches" do
it "closes issues" do
- execute_service(project, user, @oldrev, @newrev, @ref )
+ execute_service(project, commit_author, @oldrev, @newrev, @ref )
expect(Issue.find(issue.id)).to be_closed
end
it "adds a note indicating that the issue is now closed" do
expect(SystemNoteService).to receive(:change_status).with(issue, project, commit_author, "closed", closing_commit)
- execute_service(project, user, @oldrev, @newrev, @ref )
+ execute_service(project, commit_author, @oldrev, @newrev, @ref )
end
it "doesn't create additional cross-reference notes" do
expect(SystemNoteService).not_to receive(:cross_reference)
- execute_service(project, user, @oldrev, @newrev, @ref )
+ execute_service(project, commit_author, @oldrev, @newrev, @ref )
end
it "doesn't close issues when external issue tracker is in use" do
@@ -277,7 +303,7 @@ describe GitPushService, services: true do
# The push still shouldn't create cross-reference notes.
expect do
- execute_service(project, user, @oldrev, @newrev, 'refs/heads/hurf' )
+ execute_service(project, commit_author, @oldrev, @newrev, 'refs/heads/hurf' )
end.not_to change { Note.where(project_id: project.id, system: true).count }
end
end
@@ -299,7 +325,6 @@ describe GitPushService, services: true do
end
end
- # EE-only tests
context "for jira issue tracker" do
include JiraServiceHelper
@@ -349,7 +374,7 @@ describe GitPushService, services: true do
}
}.to_json
- execute_service(project, user, @oldrev, @newrev, @ref )
+ execute_service(project, commit_author, @oldrev, @newrev, @ref )
expect(WebMock).to have_requested(:post, jira_api_transition_url).with(
body: transition_body
).once
@@ -360,7 +385,7 @@ describe GitPushService, services: true do
body: "Issue solved with [#{closing_commit.id}|http://localhost/#{project.path_with_namespace}/commit/#{closing_commit.id}]."
}.to_json
- execute_service(project, user, @oldrev, @newrev, @ref )
+ execute_service(project, commit_author, @oldrev, @newrev, @ref )
expect(WebMock).to have_requested(:post, jira_api_comment_url).with(
body: comment_body
).once
@@ -383,6 +408,45 @@ describe GitPushService, services: true do
end
end
+ describe "housekeeping" do
+ let(:housekeeping) { Projects::HousekeepingService.new(project) }
+
+ before do
+ allow(Projects::HousekeepingService).to receive(:new).and_return(housekeeping)
+ end
+
+ it 'does not perform housekeeping when not needed' do
+ expect(housekeeping).not_to receive(:execute)
+
+ execute_service(project, user, @oldrev, @newrev, @ref)
+ end
+
+ context 'when housekeeping is needed' do
+ before do
+ allow(housekeeping).to receive(:needed?).and_return(true)
+ end
+
+ it 'performs housekeeping' do
+ expect(housekeeping).to receive(:execute)
+
+ execute_service(project, user, @oldrev, @newrev, @ref)
+ end
+
+ it 'does not raise an exception' do
+ allow(housekeeping).to receive(:try_obtain_lease).and_return(false)
+
+ execute_service(project, user, @oldrev, @newrev, @ref)
+ end
+ end
+
+
+ it 'increments the push counter' do
+ expect(housekeeping).to receive(:increment!)
+
+ execute_service(project, user, @oldrev, @newrev, @ref)
+ end
+ end
+
def execute_service(project, user, oldrev, newrev, ref)
service = described_class.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref )
service.execute
diff --git a/spec/services/git_tag_push_service_spec.rb b/spec/services/git_tag_push_service_spec.rb
index b982274c529..cc780587e74 100644
--- a/spec/services/git_tag_push_service_spec.rb
+++ b/spec/services/git_tag_push_service_spec.rb
@@ -78,8 +78,8 @@ describe GitTagPushService, services: true do
end
end
- describe "Web Hooks" do
- context "execute web hooks" do
+ describe "Webhooks" do
+ context "execute webhooks" do
it "when pushing tags" do
expect(project).to receive(:execute_hooks)
service.execute(project, user, 'oldrev', 'newrev', 'refs/tags/v1.0.0')
diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb
new file mode 100644
index 00000000000..14cc20e529a
--- /dev/null
+++ b/spec/services/issues/move_service_spec.rb
@@ -0,0 +1,213 @@
+require 'spec_helper'
+
+describe Issues::MoveService, services: true do
+ let(:user) { create(:user) }
+ let(:author) { create(:user) }
+ let(:title) { 'Some issue' }
+ let(:description) { 'Some issue description' }
+ let(:old_project) { create(:project) }
+ let(:new_project) { create(:project) }
+
+ let(:old_issue) do
+ create(:issue, title: title, description: description,
+ project: old_project, author: author)
+ end
+
+ let(:move_service) do
+ described_class.new(old_project, user)
+ end
+
+ shared_context 'user can move issue' do
+ before do
+ old_project.team << [user, :reporter]
+ new_project.team << [user, :reporter]
+ end
+ end
+
+ describe '#execute' do
+ shared_context 'issue move executed' do
+ let!(:new_issue) { move_service.execute(old_issue, new_project) }
+ end
+
+ context 'issue movable' do
+ include_context 'user can move issue'
+
+ context 'generic issue' do
+ include_context 'issue move executed'
+
+ it 'creates a new issue in a new project' do
+ expect(new_issue.project).to eq new_project
+ end
+
+ it 'rewrites issue title' do
+ expect(new_issue.title).to eq title
+ end
+
+ it 'rewrites issue description' do
+ expect(new_issue.description).to eq description
+ end
+
+ it 'adds system note to old issue at the end' do
+ expect(old_issue.notes.last.note).to match /^Moved to/
+ end
+
+ it 'adds system note to new issue at the end' do
+ expect(new_issue.notes.last.note).to match /^Moved from/
+ end
+
+ it 'closes old issue' do
+ expect(old_issue.closed?).to be true
+ end
+
+ it 'persists new issue' do
+ expect(new_issue.persisted?).to be true
+ end
+
+ it 'persists all changes' do
+ expect(old_issue.changed?).to be false
+ expect(new_issue.changed?).to be false
+ end
+
+ it 'preserves author' do
+ expect(new_issue.author).to eq author
+ end
+
+ it 'removes data that is invalid in new context' do
+ expect(new_issue.milestone).to be_nil
+ expect(new_issue.labels).to be_empty
+ end
+
+ it 'creates a new internal id for issue' do
+ expect(new_issue.iid).to be 1
+ end
+
+ it 'marks issue as moved' do
+ expect(old_issue.moved?).to eq true
+ expect(old_issue.moved_to).to eq new_issue
+ end
+ end
+
+ context 'issue with notes' do
+ context 'notes without references' do
+ let(:notes_params) do
+ [{ system: false, note: 'Some comment 1' },
+ { system: true, note: 'Some system note' },
+ { system: false, note: 'Some comment 2' }]
+ end
+
+ let(:notes_contents) { notes_params.map { |n| n[:note] } }
+
+ before do
+ note_params = { noteable: old_issue, project: old_project, author: author }
+ notes_params.each do |note|
+ create(:note, note_params.merge(note))
+ end
+ end
+
+ include_context 'issue move executed'
+
+ let(:all_notes) { new_issue.notes.order('id ASC') }
+ let(:system_notes) { all_notes.system }
+ let(:user_notes) { all_notes.user }
+
+ it 'rewrites existing notes in valid order' do
+ expect(all_notes.pluck(:note).first(3)).to eq notes_contents
+ end
+
+ it 'adds a system note about move after rewritten notes' do
+ expect(system_notes.last.note).to match /^Moved from/
+ end
+
+ it 'preserves orignal author of comment' do
+ expect(user_notes.pluck(:author_id)).to all(eq(author.id))
+ end
+
+ it 'preserves time when note has been created at' do
+ expect(old_issue.notes.first.created_at)
+ .to eq new_issue.notes.first.created_at
+ end
+ end
+
+ context 'notes with references' do
+ before do
+ create(:merge_request, source_project: old_project)
+ create(:note, noteable: old_issue, project: old_project, author: author,
+ note: 'Note with reference to merge request !1')
+ end
+
+ include_context 'issue move executed'
+ let(:new_note) { new_issue.notes.first }
+
+ it 'rewrites references using a cross reference to old project' do
+ expect(new_note.note)
+ .to eq "Note with reference to merge request #{old_project.to_reference}!1"
+ end
+ end
+ end
+
+ describe 'rewritting references' do
+ include_context 'issue move executed'
+
+ context 'issue reference' do
+ let(:another_issue) { create(:issue, project: old_project) }
+ let(:description) { "Some description #{another_issue.to_reference}" }
+
+ it 'rewrites referenced issues creating cross project reference' do
+ expect(new_issue.description)
+ .to eq "Some description #{old_project.to_reference}#{another_issue.to_reference}"
+ end
+ end
+ end
+
+ context 'moving to same project' do
+ let(:new_project) { old_project }
+
+ it 'raises error' do
+ expect { move_service.execute(old_issue, new_project) }
+ .to raise_error(StandardError, /Cannot move issue/)
+ end
+ end
+ end
+
+ describe 'move permissions' do
+ let(:move) { move_service.execute(old_issue, new_project) }
+
+ context 'user is reporter in both projects' do
+ include_context 'user can move issue'
+ it { expect { move }.to_not raise_error }
+ end
+
+ context 'user is reporter only in new project' do
+ before { new_project.team << [user, :reporter] }
+ it { expect { move }.to raise_error(StandardError, /permissions/) }
+ end
+
+ context 'user is reporter only in old project' do
+ before { old_project.team << [user, :reporter] }
+ it { expect { move }.to raise_error(StandardError, /permissions/) }
+ end
+
+ context 'user is reporter in one project and guest in another' do
+ before do
+ new_project.team << [user, :guest]
+ old_project.team << [user, :reporter]
+ end
+
+ it { expect { move }.to raise_error(StandardError, /permissions/) }
+ end
+
+ context 'issue has already been moved' do
+ include_context 'user can move issue'
+
+ let(:moved_to_issue) { create(:issue) }
+
+ let(:old_issue) do
+ create(:issue, project: old_project, author: author,
+ moved_to: moved_to_issue)
+ end
+
+ it { expect { move }.to raise_error(StandardError, /permissions/) }
+ end
+ end
+ end
+end
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index e579e49dfa7..4ffe753fef5 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -6,6 +6,7 @@ describe Issues::UpdateService, services: true do
let(:user3) { create(:user) }
let(:issue) { create(:issue, title: 'Old title', assignee_id: user3.id) }
let(:label) { create(:label) }
+ let(:label2) { create(:label) }
let(:project) { issue.project }
before do
@@ -48,7 +49,7 @@ describe Issues::UpdateService, services: true do
it { expect(@issue.assignee).to eq(user2) }
it { expect(@issue).to be_closed }
it { expect(@issue.labels.count).to eq(1) }
- it { expect(@issue.labels.first.title).to eq('Bug') }
+ it { expect(@issue.labels.first.title).to eq(label.name) }
it 'should send email to user2 about assign of new issue and email to user3 about issue unassignment' do
deliveries = ActionMailer::Base.deliveries
@@ -148,6 +149,48 @@ describe Issues::UpdateService, services: true do
end
end
+ context 'when the issue is relabeled' do
+ let!(:non_subscriber) { create(:user) }
+ let!(:subscriber) { create(:user).tap { |u| label.toggle_subscription(u) } }
+
+ it 'sends notifications for subscribers of newly added labels' do
+ opts = { label_ids: [label.id] }
+
+ perform_enqueued_jobs do
+ @issue = Issues::UpdateService.new(project, user, opts).execute(issue)
+ end
+
+ should_email(subscriber)
+ should_not_email(non_subscriber)
+ end
+
+ context 'when issue has the `label` label' do
+ before { issue.labels << label }
+
+ it 'does not send notifications for existing labels' do
+ opts = { label_ids: [label.id, label2.id] }
+
+ perform_enqueued_jobs do
+ @issue = Issues::UpdateService.new(project, user, opts).execute(issue)
+ end
+
+ should_not_email(subscriber)
+ should_not_email(non_subscriber)
+ end
+
+ it 'does not send notifications for removed labels' do
+ opts = { label_ids: [label2.id] }
+
+ perform_enqueued_jobs do
+ @issue = Issues::UpdateService.new(project, user, opts).execute(issue)
+ end
+
+ should_not_email(subscriber)
+ should_not_email(non_subscriber)
+ end
+ end
+ end
+
context 'when Issue has tasks' do
before { update_issue({ description: "- [ ] Task 1\n- [ ] Task 2" }) }
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 99703c7a8ec..cb8cff2fa8c 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -7,6 +7,7 @@ describe MergeRequests::UpdateService, services: true do
let(:merge_request) { create(:merge_request, :simple, title: 'Old title', assignee_id: user3.id) }
let(:project) { merge_request.project }
let(:label) { create(:label) }
+ let(:label2) { create(:label) }
before do
project.team << [user, :master]
@@ -53,7 +54,7 @@ describe MergeRequests::UpdateService, services: true do
it { expect(@merge_request.assignee).to eq(user2) }
it { expect(@merge_request).to be_closed }
it { expect(@merge_request.labels.count).to eq(1) }
- it { expect(@merge_request.labels.first.title).to eq('Bug') }
+ it { expect(@merge_request.labels.first.title).to eq(label.name) }
it { expect(@merge_request.target_branch).to eq('target') }
it 'should execute hooks with update action' do
@@ -176,6 +177,48 @@ describe MergeRequests::UpdateService, services: true do
end
end
+ context 'when the issue is relabeled' do
+ let!(:non_subscriber) { create(:user) }
+ let!(:subscriber) { create(:user).tap { |u| label.toggle_subscription(u) } }
+
+ it 'sends notifications for subscribers of newly added labels' do
+ opts = { label_ids: [label.id] }
+
+ perform_enqueued_jobs do
+ @merge_request = MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
+ end
+
+ should_email(subscriber)
+ should_not_email(non_subscriber)
+ end
+
+ context 'when issue has the `label` label' do
+ before { merge_request.labels << label }
+
+ it 'does not send notifications for existing labels' do
+ opts = { label_ids: [label.id, label2.id] }
+
+ perform_enqueued_jobs do
+ @merge_request = MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
+ end
+
+ should_not_email(subscriber)
+ should_not_email(non_subscriber)
+ end
+
+ it 'does not send notifications for removed labels' do
+ opts = { label_ids: [label2.id] }
+
+ perform_enqueued_jobs do
+ @merge_request = MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
+ end
+
+ should_not_email(subscriber)
+ should_not_email(non_subscriber)
+ end
+ end
+ end
+
context 'when MergeRequest has tasks' do
before { update_merge_request({ description: "- [ ] Task 1\n- [ ] Task 2" }) }
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 2d0b5df4224..b5407397c1d 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -224,6 +224,15 @@ describe NotificationService, services: true do
should_not_email(issue.assignee)
end
+
+ it "emails subscribers of the issue's labels" do
+ subscriber = create(:user)
+ label = create(:label, issues: [issue])
+ label.toggle_subscription(subscriber)
+ notification.new_issue(issue, @u_disabled)
+
+ should_email(subscriber)
+ end
end
describe :reassigned_issue do
@@ -296,6 +305,35 @@ describe NotificationService, services: true do
end
end
+ describe '#relabeled_issue' do
+ let(:label) { create(:label, issues: [issue]) }
+ let(:label2) { create(:label) }
+ let!(:subscriber_to_label) { create(:user).tap { |u| label.toggle_subscription(u) } }
+ let!(:subscriber_to_label2) { create(:user).tap { |u| label2.toggle_subscription(u) } }
+
+ it "emails subscribers of the issue's added labels only" do
+ notification.relabeled_issue(issue, [label2], @u_disabled)
+
+ should_not_email(subscriber_to_label)
+ should_email(subscriber_to_label2)
+ end
+
+ it "doesn't send email to anyone but subscribers of the given labels" do
+ notification.relabeled_issue(issue, [label2], @u_disabled)
+
+ should_not_email(issue.assignee)
+ should_not_email(issue.author)
+ should_not_email(@u_watcher)
+ should_not_email(@u_participant_mentioned)
+ should_not_email(@subscriber)
+ should_not_email(@watcher_and_subscriber)
+ should_not_email(@unsubscriber)
+ should_not_email(@u_participating)
+ should_not_email(subscriber_to_label)
+ should_email(subscriber_to_label2)
+ end
+ end
+
describe :close_issue do
it 'should sent email to issue assignee and issue author' do
notification.close_issue(issue, @u_disabled)
@@ -349,6 +387,15 @@ describe NotificationService, services: true do
should_not_email(@u_participating)
should_not_email(@u_disabled)
end
+
+ it "emails subscribers of the merge request's labels" do
+ subscriber = create(:user)
+ label = create(:label, merge_requests: [merge_request])
+ label.toggle_subscription(subscriber)
+ notification.new_merge_request(merge_request, @u_disabled)
+
+ should_email(subscriber)
+ end
end
describe :reassigned_merge_request do
@@ -366,6 +413,35 @@ describe NotificationService, services: true do
end
end
+ describe :relabel_merge_request do
+ let(:label) { create(:label, merge_requests: [merge_request]) }
+ let(:label2) { create(:label) }
+ let!(:subscriber_to_label) { create(:user).tap { |u| label.toggle_subscription(u) } }
+ let!(:subscriber_to_label2) { create(:user).tap { |u| label2.toggle_subscription(u) } }
+
+ it "emails subscribers of the merge request's added labels only" do
+ notification.relabeled_merge_request(merge_request, [label2], @u_disabled)
+
+ should_not_email(subscriber_to_label)
+ should_email(subscriber_to_label2)
+ end
+
+ it "doesn't send email to anyone but subscribers of the given labels" do
+ notification.relabeled_merge_request(merge_request, [label2], @u_disabled)
+
+ should_not_email(merge_request.assignee)
+ should_not_email(merge_request.author)
+ should_not_email(@u_watcher)
+ should_not_email(@u_participant_mentioned)
+ should_not_email(@subscriber)
+ should_not_email(@watcher_and_subscriber)
+ should_not_email(@unsubscriber)
+ should_not_email(@u_participating)
+ should_not_email(subscriber_to_label)
+ should_email(subscriber_to_label2)
+ end
+ end
+
describe :closed_merge_request do
it do
notification.close_mr(merge_request, @u_disabled)
@@ -467,16 +543,4 @@ describe NotificationService, services: true do
# Make the watcher a subscriber to detect dupes
issuable.subscriptions.create(user: @watcher_and_subscriber, subscribed: true)
end
-
- def sent_to_user?(user)
- ActionMailer::Base.deliveries.map(&:to).flatten.count(user.email) == 1
- end
-
- def should_email(user)
- expect(sent_to_user?(user)).to be_truthy
- end
-
- def should_not_email(user)
- expect(sent_to_user?(user)).to be_falsey
- end
end
diff --git a/spec/services/projects/autocomplete_service_spec.rb b/spec/services/projects/autocomplete_service_spec.rb
new file mode 100644
index 00000000000..6108c26a78b
--- /dev/null
+++ b/spec/services/projects/autocomplete_service_spec.rb
@@ -0,0 +1,79 @@
+require 'spec_helper'
+
+describe Projects::AutocompleteService, services: true do
+ describe '#issues' do
+ describe 'confidential issues' do
+ let(:author) { create(:user) }
+ let(:assignee) { create(:user) }
+ let(:non_member) { create(:user) }
+ let(:member) { create(:user) }
+ let(:admin) { create(:admin) }
+ let(:project) { create(:empty_project, :public) }
+ let!(:issue) { create(:issue, project: project, title: 'Issue 1') }
+ let!(:security_issue_1) { create(:issue, :confidential, project: project, title: 'Security issue 1', author: author) }
+ let!(:security_issue_2) { create(:issue, :confidential, title: 'Security issue 2', project: project, assignee: assignee) }
+
+ it 'should not list project confidential issues for guests' do
+ autocomplete = described_class.new(project, nil)
+ issues = autocomplete.issues.map(&:iid)
+
+ expect(issues).to include issue.iid
+ expect(issues).not_to include security_issue_1.iid
+ expect(issues).not_to include security_issue_2.iid
+ expect(issues.count).to eq 1
+ end
+
+ it 'should not list project confidential issues for non project members' do
+ autocomplete = described_class.new(project, non_member)
+ issues = autocomplete.issues.map(&:iid)
+
+ expect(issues).to include issue.iid
+ expect(issues).not_to include security_issue_1.iid
+ expect(issues).not_to include security_issue_2.iid
+ expect(issues.count).to eq 1
+ end
+
+ it 'should list project confidential issues for author' do
+ autocomplete = described_class.new(project, author)
+ issues = autocomplete.issues.map(&:iid)
+
+ expect(issues).to include issue.iid
+ expect(issues).to include security_issue_1.iid
+ expect(issues).not_to include security_issue_2.iid
+ expect(issues.count).to eq 2
+ end
+
+ it 'should list project confidential issues for assignee' do
+ autocomplete = described_class.new(project, assignee)
+ issues = autocomplete.issues.map(&:iid)
+
+ expect(issues).to include issue.iid
+ expect(issues).not_to include security_issue_1.iid
+ expect(issues).to include security_issue_2.iid
+ expect(issues.count).to eq 2
+ end
+
+ it 'should list project confidential issues for project members' do
+ project.team << [member, :developer]
+
+ autocomplete = described_class.new(project, member)
+ issues = autocomplete.issues.map(&:iid)
+
+ expect(issues).to include issue.iid
+ expect(issues).to include security_issue_1.iid
+ expect(issues).to include security_issue_2.iid
+ expect(issues.count).to eq 3
+ end
+
+ it 'should list all project issues for admin' do
+ autocomplete = described_class.new(project, admin)
+ issues = autocomplete.issues.map(&:iid)
+
+ expect(issues).to include issue.iid
+ expect(issues).to include security_issue_1.iid
+ expect(issues).to include security_issue_2.iid
+ expect(issues.count).to eq 3
+ end
+ end
+ end
+end
diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb
new file mode 100644
index 00000000000..4c5ced7e746
--- /dev/null
+++ b/spec/services/projects/housekeeping_service_spec.rb
@@ -0,0 +1,48 @@
+require 'spec_helper'
+
+describe Projects::HousekeepingService do
+ subject { Projects::HousekeepingService.new(project) }
+ let(:project) { create :project }
+
+ describe 'execute' do
+ before do
+ project.pushes_since_gc = 3
+ project.save!
+ end
+
+ it 'enqueues a sidekiq job' do
+ expect(subject).to receive(:try_obtain_lease).and_return(true)
+ expect(GitlabShellOneShotWorker).to receive(:perform_async).with(:gc, project.path_with_namespace)
+
+ subject.execute
+ expect(project.pushes_since_gc).to eq(0)
+ end
+
+ it 'does not enqueue a job when no lease can be obtained' do
+ expect(subject).to receive(:try_obtain_lease).and_return(false)
+ expect(GitlabShellOneShotWorker).not_to receive(:perform_async)
+
+ expect { subject.execute }.to raise_error(Projects::HousekeepingService::LeaseTaken)
+ expect(project.pushes_since_gc).to eq(0)
+ end
+ end
+
+ describe 'needed?' do
+ it 'when the count is low enough' do
+ expect(subject.needed?).to eq(false)
+ end
+
+ it 'when the count is high enough' do
+ allow(project).to receive(:pushes_since_gc).and_return(10)
+ expect(subject.needed?).to eq(true)
+ end
+ end
+
+ describe 'increment!' do
+ it 'increments the pushes_since_gc counter' do
+ expect(project.pushes_since_gc).to eq(0)
+ subject.increment!
+ expect(project.pushes_since_gc).to eq(1)
+ end
+ end
+end
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index 3c06a890163..e8b9e6b9238 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -102,8 +102,8 @@ describe Projects::UpdateService, services: true do
describe :visibility_level do
let(:user) { create :user, admin: true }
- let(:project) { create :project, visibility_level: Gitlab::VisibilityLevel::INTERNAL }
- let(:forked_project) { create :forked_project_with_submodules, visibility_level: Gitlab::VisibilityLevel::INTERNAL }
+ let(:project) { create(:project, :internal) }
+ let(:forked_project) { create(:forked_project_with_submodules, :internal) }
let(:opts) { {} }
before do
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 5dcc39f5fdc..240eae10052 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -280,6 +280,18 @@ describe SystemNoteService, services: true do
end
end
+ describe '.new_issue_branch' do
+ subject { described_class.new_issue_branch(noteable, project, author, "1-mepmep") }
+
+ it_behaves_like 'a system note'
+
+ context 'when a branch is created from the new branch button' do
+ it 'sets the note text' do
+ expect(subject.note).to match /\AStarted branch [`1-mepmep`]/
+ end
+ end
+ end
+
describe '.cross_reference' do
subject { described_class.cross_reference(noteable, mentioner, author) }
@@ -441,6 +453,59 @@ describe SystemNoteService, services: true do
end
end
+ describe '.noteable_moved' do
+ let(:new_project) { create(:project) }
+ let(:new_noteable) { create(:issue, project: new_project) }
+
+ subject do
+ described_class.noteable_moved(noteable, project, new_noteable, author, direction: direction)
+ end
+
+ shared_examples 'cross project mentionable' do
+ include GitlabMarkdownHelper
+
+ it 'should contain cross reference to new noteable' do
+ expect(subject.note).to include cross_project_reference(new_project, new_noteable)
+ end
+
+ it 'should mention referenced noteable' do
+ expect(subject.note).to include new_noteable.to_reference
+ end
+
+ it 'should mention referenced project' do
+ expect(subject.note).to include new_project.to_reference
+ end
+ end
+
+ context 'moved to' do
+ let(:direction) { :to }
+
+ it_behaves_like 'cross project mentionable'
+
+ it 'should notify about noteable being moved to' do
+ expect(subject.note).to match /Moved to/
+ end
+ end
+
+ context 'moved from' do
+ let(:direction) { :from }
+
+ it_behaves_like 'cross project mentionable'
+
+ it 'should notify about noteable being moved from' do
+ expect(subject.note).to match /Moved from/
+ end
+ end
+
+ context 'invalid direction' do
+ let(:direction) { :invalid }
+
+ it 'should raise error' do
+ expect { subject }.to raise_error StandardError, /Invalid direction/
+ end
+ end
+ end
+
include JiraServiceHelper
describe 'JIRA integration' do
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
index 96420acb31d..b4728807b8b 100644
--- a/spec/services/todo_service_spec.rb
+++ b/spec/services/todo_service_spec.rb
@@ -148,8 +148,13 @@ describe TodoService, services: true do
should_not_create_todo(user: stranger, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
end
- it 'does not create todo when leaving a note on commit' do
- should_not_create_any_todo { service.new_note(note_on_commit, john_doe) }
+ it 'creates a todo for each valid mentioned user when leaving a note on commit' do
+ service.new_note(note_on_commit, john_doe)
+
+ should_create_todo(user: michael, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
+ should_create_todo(user: author, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
+ should_not_create_todo(user: john_doe, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
+ should_not_create_todo(user: stranger, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
end
it 'does not create todo when leaving a note on snippet' do
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 8f381f46e57..596d607f2a1 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -14,7 +14,7 @@ require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'shoulda/matchers'
require 'sidekiq/testing/inline'
-require 'benchmark/ips'
+require 'rspec/retry'
# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
@@ -25,16 +25,19 @@ RSpec.configure do |config|
config.use_instantiated_fixtures = false
config.mock_with :rspec
+ config.verbose_retry = true
+ config.display_try_failure_messages = true
+
config.include Devise::TestHelpers, type: :controller
config.include LoginHelpers, type: :feature
config.include LoginHelpers, type: :request
config.include StubConfiguration
+ config.include EmailHelpers
config.include RelativeUrl, type: feature
config.include TestEnv
config.include ActiveJob::TestHelper
config.include StubGitlabCalls
config.include StubGitlabData
- config.include BenchmarkMatchers, benchmark: true
config.infer_spec_type_from_file_location!
config.raise_errors_for_deprecations!
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index 65d59e6813c..e1f90e17cce 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -10,7 +10,7 @@ Capybara.register_driver :poltergeist do |app|
Capybara::Poltergeist::Driver.new(app, js_errors: true, timeout: timeout, window_size: [1366, 768])
end
-Capybara.default_wait_time = timeout
+Capybara.default_max_wait_time = timeout
Capybara.ignore_hidden_elements = true
unless ENV['CI'] || ENV['CI_SERVER']
diff --git a/spec/support/email_helpers.rb b/spec/support/email_helpers.rb
new file mode 100644
index 00000000000..a85ab22ce36
--- /dev/null
+++ b/spec/support/email_helpers.rb
@@ -0,0 +1,13 @@
+module EmailHelpers
+ def sent_to_user?(user)
+ ActionMailer::Base.deliveries.map(&:to).flatten.count(user.email) == 1
+ end
+
+ def should_email(user)
+ expect(sent_to_user?(user)).to be_truthy
+ end
+
+ def should_not_email(user)
+ expect(sent_to_user?(user)).to be_falsey
+ end
+end
diff --git a/spec/support/matchers/access_matchers.rb b/spec/support/matchers/access_matchers.rb
index 558e8b1612f..4e007c777e3 100644
--- a/spec/support/matchers/access_matchers.rb
+++ b/spec/support/matchers/access_matchers.rb
@@ -15,6 +15,8 @@ module AccessMatchers
logout
when :admin
login_as(create(:admin))
+ when :external
+ login_as(create(:user, external: true))
when User
login_as(user)
else
diff --git a/spec/support/matchers/benchmark_matchers.rb b/spec/support/matchers/benchmark_matchers.rb
deleted file mode 100644
index 84f655c2119..00000000000
--- a/spec/support/matchers/benchmark_matchers.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-module BenchmarkMatchers
- extend RSpec::Matchers::DSL
-
- def self.included(into)
- into.extend(ClassMethods)
- end
-
- matcher :iterate_per_second do |min_iterations|
- supports_block_expectations
-
- match do |block|
- @max_stddev ||= 30
-
- @entry = benchmark(&block)
-
- expect(@entry.ips).to be >= min_iterations
- expect(@entry.stddev_percentage).to be <= @max_stddev
- end
-
- chain :with_maximum_stddev do |value|
- @max_stddev = value
- end
-
- description do
- "run at least #{min_iterations} iterations per second"
- end
-
- failure_message do
- ips = @entry.ips.round(2)
- stddev = @entry.stddev_percentage.round(2)
-
- "expected at least #{min_iterations} iterations per second " \
- "with a maximum stddev of #{@max_stddev}%, instead of " \
- "#{ips} iterations per second with a stddev of #{stddev}%"
- end
- end
-
- # Benchmarks the given block and returns a Benchmark::IPS::Report::Entry.
- def benchmark(&block)
- report = Benchmark.ips(quiet: true) do |bench|
- bench.report do
- instance_eval(&block)
- end
- end
-
- report.entries[0]
- end
-
- module ClassMethods
- # Wraps around rspec's subject method so you can write:
- #
- # benchmark_subject { SomeClass.some_method }
- #
- # instead of:
- #
- # subject { -> { SomeClass.some_method } }
- def benchmark_subject(&block)
- subject { block }
- end
- end
-end
diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb
index fce91015fd4..e876d44c166 100644
--- a/spec/support/mentionable_shared_examples.rb
+++ b/spec/support/mentionable_shared_examples.rb
@@ -52,6 +52,8 @@ shared_context 'mentionable context' do
end
set_mentionable_text.call(ref_string)
+
+ project.team << [author, :developer]
end
end
diff --git a/spec/support/wait_for_ajax.rb b/spec/support/wait_for_ajax.rb
index 692d219e9f1..b90fc112671 100644
--- a/spec/support/wait_for_ajax.rb
+++ b/spec/support/wait_for_ajax.rb
@@ -1,6 +1,6 @@
module WaitForAjax
def wait_for_ajax
- Timeout.timeout(Capybara.default_wait_time) do
+ Timeout.timeout(Capybara.default_max_wait_time) do
loop until finished_all_ajax_requests?
end
end
diff --git a/spec/workers/delete_user_worker_spec.rb b/spec/workers/delete_user_worker_spec.rb
new file mode 100644
index 00000000000..14c56521280
--- /dev/null
+++ b/spec/workers/delete_user_worker_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe DeleteUserWorker do
+ let!(:user) { create(:user) }
+ let!(:current_user) { create(:user) }
+
+ it "calls the DeleteUserWorker with the params it was given" do
+ expect_any_instance_of(DeleteUserService).to receive(:execute).
+ with(user, {})
+
+ DeleteUserWorker.new.perform(current_user.id, user.id)
+ end
+
+ it "uses symbolized keys" do
+ expect_any_instance_of(DeleteUserService).to receive(:execute).
+ with(user, test: "test")
+
+ DeleteUserWorker.new.perform(current_user.id, user.id, "test" => "test")
+ end
+end
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index e4151b9bb6a..0265dbe9c66 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -11,7 +11,7 @@ describe PostReceive do
end
end
- context "web hook" do
+ context "webhook" do
let(:project) { create(:project) }
let(:key) { create(:key, user: project.owner) }
let(:key_id) { key.shell_id }
diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb
index 172537474ee..4ef05eb29d2 100644
--- a/spec/workers/repository_fork_worker_spec.rb
+++ b/spec/workers/repository_fork_worker_spec.rb
@@ -3,12 +3,17 @@ require 'spec_helper'
describe RepositoryForkWorker do
let(:project) { create(:project) }
let(:fork_project) { create(:project, forked_from_project: project) }
+ let(:shell) { Gitlab::Shell.new }
subject { RepositoryForkWorker.new }
+ before do
+ allow(subject).to receive(:gitlab_shell).and_return(shell)
+ end
+
describe "#perform" do
it "creates a new repository from a fork" do
- expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository).with(
+ expect(shell).to receive(:fork_repository).with(
project.path_with_namespace,
fork_project.namespace.path
).and_return(true)
@@ -19,20 +24,26 @@ describe RepositoryForkWorker do
fork_project.namespace.path)
end
- it 'flushes the empty caches' do
- expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository).
+ it 'flushes various caches' do
+ expect(shell).to receive(:fork_repository).
with(project.path_with_namespace, fork_project.namespace.path).
and_return(true)
expect_any_instance_of(Repository).to receive(:expire_emptiness_caches).
and_call_original
+ expect_any_instance_of(Repository).to receive(:expire_exists_cache).
+ and_call_original
+
subject.perform(project.id, project.path_with_namespace,
fork_project.namespace.path)
end
it "handles bad fork" do
- expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository).and_return(false)
+ expect(shell).to receive(:fork_repository).and_return(false)
+
+ expect(subject.logger).to receive(:error)
+
subject.perform(
project.id,
project.path_with_namespace,