diff options
| author | Zeger-Jan van de Weg <mail@zjvandeweg.nl> | 2015-12-10 16:27:26 +0100 |
|---|---|---|
| committer | Zeger-Jan van de Weg <mail@zjvandeweg.nl> | 2015-12-10 16:27:26 +0100 |
| commit | 82d2e5c3c561f213a59c1df36c6d5c4bb2eef7d0 (patch) | |
| tree | 0e6e0efcc6dc6ff1b517c82fb3b2a86cf78e8e4f /spec | |
| parent | 5fd280f3d3264aec3656cb61cd8728f2ca4d61ce (diff) | |
| parent | 4e5897f51ef97d7c3ff6c57f81521f552979a3da (diff) | |
| download | gitlab-ce-82d2e5c3c561f213a59c1df36c6d5c4bb2eef7d0.tar.gz | |
Merge branch 'master' into copying-file-seen-as-licence
Diffstat (limited to 'spec')
287 files changed, 6649 insertions, 3357 deletions
diff --git a/spec/benchmarks/finders/issues_finder_spec.rb b/spec/benchmarks/finders/issues_finder_spec.rb new file mode 100644 index 00000000000..b57a33004a4 --- /dev/null +++ b/spec/benchmarks/finders/issues_finder_spec.rb @@ -0,0 +1,55 @@ +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/controllers/abuse_reports_controller_spec.rb b/spec/controllers/abuse_reports_controller_spec.rb index 0faab8d7ff0..15824a1c67f 100644 --- a/spec/controllers/abuse_reports_controller_spec.rb +++ b/spec/controllers/abuse_reports_controller_spec.rb @@ -18,27 +18,31 @@ describe AbuseReportsController do end it "sends a notification email" do - post :create, - abuse_report: { - user_id: user.id, - message: message - } - - email = ActionMailer::Base.deliveries.last - - expect(email.to).to eq([admin_email]) - expect(email.subject).to include(user.username) - expect(email.text_part.body).to include(message) - end - - it "saves the abuse report" do - expect do + perform_enqueued_jobs do post :create, abuse_report: { user_id: user.id, message: message } - end.to change { AbuseReport.count }.by(1) + + email = ActionMailer::Base.deliveries.last + + expect(email.to).to eq([admin_email]) + expect(email.subject).to include(user.username) + expect(email.text_part.body).to include(message) + end + end + + it "saves the abuse report" do + perform_enqueued_jobs do + expect do + post :create, + abuse_report: { + user_id: user.id, + message: message + } + end.to change { AbuseReport.count }.by(1) + end end end diff --git a/spec/controllers/admin/impersonation_controller_spec.rb b/spec/controllers/admin/impersonation_controller_spec.rb new file mode 100644 index 00000000000..d7a7ba1c5b6 --- /dev/null +++ b/spec/controllers/admin/impersonation_controller_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe Admin::ImpersonationController do + let(:admin) { create(:admin) } + + before do + sign_in(admin) + end + + describe 'CREATE #impersonation when blocked' do + let(:blocked_user) { create(:user, state: :blocked) } + + it 'does not allow impersonation' do + post :create, id: blocked_user.username + + expect(flash[:alert]).to eq 'You cannot impersonate a blocked user' + end + end +end diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb index fcbe62cace8..8b7af4d3a0a 100644 --- a/spec/controllers/admin/users_controller_spec.rb +++ b/spec/controllers/admin/users_controller_spec.rb @@ -7,21 +7,6 @@ describe Admin::UsersController do sign_in(admin) end - describe 'POST login_as' do - let(:user) { create(:user) } - - it 'logs admin as another user' do - expect(warden.authenticate(scope: :user)).not_to eq(user) - post :login_as, id: user.username - expect(warden.authenticate(scope: :user)).to eq(user) - end - - it 'redirects user to homepage' do - post :login_as, id: user.username - expect(response).to redirect_to(root_path) - end - end - describe 'DELETE #user with projects' do let(:user) { create(:user) } let(:project) { create(:project, namespace: user.namespace) } diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb index aa8d6cb807f..85379a8e984 100644 --- a/spec/controllers/autocomplete_controller_spec.rb +++ b/spec/controllers/autocomplete_controller_spec.rb @@ -114,7 +114,7 @@ describe AutocompleteController do get(:users, project_id: project.id) end - it { expect(response.status).to eq(302) } + it { expect(response.status).to eq(404) } end describe 'GET #users with unknown project' do @@ -122,7 +122,7 @@ describe AutocompleteController do get(:users, project_id: 'unknown') end - it { expect(response.status).to eq(302) } + it { expect(response.status).to eq(404) } end describe 'GET #users with inaccessible group' do @@ -131,7 +131,7 @@ describe AutocompleteController do get(:users, group_id: user.namespace.id) end - it { expect(response.status).to eq(302) } + it { expect(response.status).to eq(404) } end describe 'GET #users with no project' do @@ -139,7 +139,8 @@ describe AutocompleteController do get(:users) end - it { expect(response.status).to eq(302) } + it { expect(body).to be_kind_of(Array) } + it { expect(body.size).to eq 0 } end end end diff --git a/spec/controllers/commit_controller_spec.rb b/spec/controllers/commit_controller_spec.rb index bb3d87f3840..7793bf1e421 100644 --- a/spec/controllers/commit_controller_spec.rb +++ b/spec/controllers/commit_controller_spec.rb @@ -69,6 +69,21 @@ describe Projects::CommitController do expect(response.body).to start_with("diff --git") end + + it "should really only be a git diff without whitespace changes" do + get(:show, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: '66eceea0db202bb39c4e445e8ca28689645366c5', + # id: commit.id, + format: format, + w: 1) + + expect(response.body).to start_with("diff --git") + # without whitespace option, there are more than 2 diff_splits + diff_splits = assigns(:diffs)[0].diff.split("\n") + expect(diff_splits.length).to be <= 2 + end end describe "as patch" do @@ -95,6 +110,26 @@ describe Projects::CommitController do expect(response.body).to match(/^diff --git/) end end + + context 'commit that removes a submodule' do + render_views + + let(:fork_project) { create(:forked_project_with_submodules) } + let(:commit) { fork_project.commit('remove-submodule') } + + before do + fork_project.team << [user, :master] + end + + it 'renders it' do + get(:show, + namespace_id: fork_project.namespace.to_param, + project_id: fork_project.to_param, + id: commit.id) + + expect(response).to be_success + end + end end describe "#branches" do diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb new file mode 100644 index 00000000000..eb0c6ac6d80 --- /dev/null +++ b/spec/controllers/groups/milestones_controller_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe Groups::MilestonesController do + let(:group) { create(:group) } + let(:project) { create(:project, group: group) } + let(:project2) { create(:empty_project, group: group) } + let(:user) { create(:user) } + let(:title) { '肯定不是中文的问题' } + + before do + sign_in(user) + group.add_owner(user) + project.team << [user, :master] + controller.instance_variable_set(:@group, group) + end + + describe "#create" do + it "should create group milestone with Chinese title" do + post :create, + group_id: group.id, + milestone: { project_ids: [project.id, project2.id], title: title } + + expect(response).to redirect_to(group_milestone_path(group, title.to_slug.to_s, title: title)) + expect(Milestone.where(title: title).count).to eq(2) + end + end +end diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb index 2a447248b70..be19f1abc53 100644 --- a/spec/controllers/projects/compare_controller_spec.rb +++ b/spec/controllers/projects/compare_controller_spec.rb @@ -23,6 +23,22 @@ describe Projects::CompareController do expect(assigns(:commits).length).to be >= 1 end + it 'compare should show some diffs with ignore whitespace change option' do + get(:show, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + from: '08f22f25', + to: '66eceea0', + w: 1) + + expect(response).to be_success + expect(assigns(:diffs).length).to be >= 1 + expect(assigns(:commits).length).to be >= 1 + # without whitespace option, there are more than 2 diff_splits + diff_splits = assigns(:diffs)[0].diff.split("\n") + expect(diff_splits.length).to be <= 2 + end + describe 'non-existent refs' do it 'invalid source ref' do get(:show, diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index b8db8591709..6aaec224f6e 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -10,6 +10,30 @@ describe Projects::MergeRequestsController do project.team << [user, :master] end + describe '#new' do + context 'merge request that removes a submodule' do + render_views + + let(:fork_project) { create(:forked_project_with_submodules) } + + before do + fork_project.team << [user, :master] + end + + it 'renders it' do + get :new, + namespace_id: fork_project.namespace.to_param, + project_id: fork_project.to_param, + merge_request: { + source_branch: 'remove-submodule', + target_branch: 'master' + } + + expect(response).to be_success + end + end + end + describe "#show" do shared_examples "export merge as" do |format| it "should generally work" do @@ -147,6 +171,34 @@ describe Projects::MergeRequestsController do end end + describe 'GET diffs with ignore_whitespace_change' do + def go(format: 'html') + get :diffs, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: merge_request.iid, + format: format, + w: 1 + end + + context 'as html' do + it 'renders the diff template' do + go + + expect(response).to render_template('diffs') + end + end + + context 'as json' do + it 'renders the diffs template to a string' do + go format: 'json' + + expect(response).to render_template('projects/merge_requests/show/_diffs') + expect(JSON.parse(response.body)).to have_key('html') + end + end + end + describe 'GET commits' do def go(format: 'html') get :commits, diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb index 8127efabe6e..d173bb350f1 100644 --- a/spec/controllers/projects/milestones_controller_spec.rb +++ b/spec/controllers/projects/milestones_controller_spec.rb @@ -5,7 +5,7 @@ describe Projects::MilestonesController do let(:user) { create(:user) } let(:milestone) { create(:milestone, project: project) } let(:issue) { create(:issue, project: project, milestone: milestone) } - let(:merge_request) { create(:merge_request, source_project: project, target_project: project, milestone: milestone) } + let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, milestone: milestone) } before do sign_in(user) @@ -15,10 +15,9 @@ describe Projects::MilestonesController do describe "#destroy" do it "should remove milestone" do - merge_request.reload expect(issue.milestone_id).to eq(milestone.id) - delete :destroy, namespace_id: project.namespace.id, project_id: project.id, id: milestone.id, format: :js + delete :destroy, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid, format: :js expect(response).to be_success expect(Event.first.action).to eq(Event::DESTROYED) diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb index c114f342021..1caa476d37d 100644 --- a/spec/controllers/projects/raw_controller_spec.rb +++ b/spec/controllers/projects/raw_controller_spec.rb @@ -33,5 +33,39 @@ describe Projects::RawController do expect(response.header['Content-Type']).to eq('image/jpeg') end end + + context 'lfs object' do + let(:id) { 'be93687/files/lfs/lfs_object.iso' } + let!(:lfs_object) { create(:lfs_object, oid: '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', size: '1575078') } + + context 'when project has access' do + before do + public_project.lfs_objects << lfs_object + allow_any_instance_of(LfsObjectUploader).to receive(:exists?).and_return(true) + allow(controller).to receive(:send_file) { controller.render nothing: true } + end + + it 'serves the file' do + expect(controller).to receive(:send_file).with("#{Gitlab.config.shared.path}/lfs-objects/91/ef/f75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897", filename: "lfs_object.iso", disposition: 'attachment') + get(:show, + namespace_id: public_project.namespace.to_param, + project_id: public_project.to_param, + id: id) + + expect(response.status).to eq(200) + end + end + + context 'when project does not have access' do + it 'does not serve the file' do + get(:show, + namespace_id: public_project.namespace.to_param, + project_id: public_project.to_param, + id: id) + + expect(response.status).to eq(404) + end + end + end end end diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 4bb47c6b025..665526fde93 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -88,6 +88,22 @@ describe ProjectsController do end end + describe "#destroy" do + let(:admin) { create(:admin) } + + it "redirects to the dashboard" do + controller.instance_variable_set(:@project, project) + sign_in(admin) + + orig_id = project.id + delete :destroy, namespace_id: project.namespace.path, id: project.path + + expect { Project.find(orig_id) }.to raise_error(ActiveRecord::RecordNotFound) + expect(response.status).to eq(302) + expect(response).to redirect_to(dashboard_projects_path) + end + end + describe "POST #toggle_star" do it "toggles star if user is signed in" do sign_in(user) diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb new file mode 100644 index 00000000000..b3dcb52c500 --- /dev/null +++ b/spec/controllers/snippets_controller_spec.rb @@ -0,0 +1,233 @@ +require 'spec_helper' + +describe SnippetsController do + describe 'GET #show' do + let(:user) { create(:user) } + + context 'when the personal snippet is private' do + let(:personal_snippet) { create(:personal_snippet, :private, author: user) } + + context 'when signed in' do + before do + sign_in(user) + end + + context 'when signed in user is not the author' do + let(:other_author) { create(:author) } + let(:other_personal_snippet) { create(:personal_snippet, :private, author: other_author) } + + it 'responds with status 404' do + get :show, id: other_personal_snippet.to_param + + expect(response.status).to eq(404) + end + end + + context 'when signed in user is the author' do + it 'renders the snippet' do + get :show, id: personal_snippet.to_param + + expect(assigns(:snippet)).to eq(personal_snippet) + expect(response.status).to eq(200) + end + end + end + + context 'when not signed in' do + it 'redirects to the sign in page' do + get :show, id: personal_snippet.to_param + + expect(response).to redirect_to(new_user_session_path) + end + end + end + + context 'when the personal snippet is internal' do + let(:personal_snippet) { create(:personal_snippet, :internal, author: user) } + + context 'when signed in' do + before do + sign_in(user) + end + + it 'renders the snippet' do + get :show, id: personal_snippet.to_param + + expect(assigns(:snippet)).to eq(personal_snippet) + expect(response.status).to eq(200) + end + end + + context 'when not signed in' do + it 'redirects to the sign in page' do + get :show, id: personal_snippet.to_param + + expect(response).to redirect_to(new_user_session_path) + end + end + end + + context 'when the personal snippet is public' do + let(:personal_snippet) { create(:personal_snippet, :public, author: user) } + + context 'when signed in' do + before do + sign_in(user) + end + + it 'renders the snippet' do + get :show, id: personal_snippet.to_param + + expect(assigns(:snippet)).to eq(personal_snippet) + expect(response.status).to eq(200) + end + end + + context 'when not signed in' do + it 'renders the snippet' do + get :show, id: personal_snippet.to_param + + expect(assigns(:snippet)).to eq(personal_snippet) + expect(response.status).to eq(200) + end + end + end + + context 'when the personal snippet does not exist' do + context 'when signed in' do + before do + sign_in(user) + end + + it 'responds with status 404' do + get :show, id: 'doesntexist' + + expect(response.status).to eq(404) + end + end + + context 'when not signed in' do + it 'responds with status 404' do + get :show, id: 'doesntexist' + + expect(response.status).to eq(404) + end + end + end + end + + describe 'GET #raw' do + let(:user) { create(:user) } + + context 'when the personal snippet is private' do + let(:personal_snippet) { create(:personal_snippet, :private, author: user) } + + context 'when signed in' do + before do + sign_in(user) + end + + context 'when signed in user is not the author' do + let(:other_author) { create(:author) } + let(:other_personal_snippet) { create(:personal_snippet, :private, author: other_author) } + + it 'responds with status 404' do + get :raw, id: other_personal_snippet.to_param + + expect(response.status).to eq(404) + end + end + + context 'when signed in user is the author' do + it 'renders the raw snippet' do + get :raw, id: personal_snippet.to_param + + expect(assigns(:snippet)).to eq(personal_snippet) + expect(response.status).to eq(200) + end + end + end + + context 'when not signed in' do + it 'redirects to the sign in page' do + get :raw, id: personal_snippet.to_param + + expect(response).to redirect_to(new_user_session_path) + end + end + end + + context 'when the personal snippet is internal' do + let(:personal_snippet) { create(:personal_snippet, :internal, author: user) } + + context 'when signed in' do + before do + sign_in(user) + end + + it 'renders the raw snippet' do + get :raw, id: personal_snippet.to_param + + expect(assigns(:snippet)).to eq(personal_snippet) + expect(response.status).to eq(200) + end + end + + context 'when not signed in' do + it 'redirects to the sign in page' do + get :raw, id: personal_snippet.to_param + + expect(response).to redirect_to(new_user_session_path) + end + end + end + + context 'when the personal snippet is public' do + let(:personal_snippet) { create(:personal_snippet, :public, author: user) } + + context 'when signed in' do + before do + sign_in(user) + end + + it 'renders the raw snippet' do + get :raw, id: personal_snippet.to_param + + expect(assigns(:snippet)).to eq(personal_snippet) + expect(response.status).to eq(200) + end + end + + context 'when not signed in' do + it 'renders the raw snippet' do + get :raw, id: personal_snippet.to_param + + expect(assigns(:snippet)).to eq(personal_snippet) + expect(response.status).to eq(200) + end + end + end + + context 'when the personal snippet does not exist' do + context 'when signed in' do + before do + sign_in(user) + end + + it 'responds with status 404' do + get :raw, id: 'doesntexist' + + expect(response.status).to eq(404) + end + end + + context 'when not signed in' do + it 'responds with status 404' do + get :raw, id: 'doesntexist' + + expect(response.status).to eq(404) + end + end + end + end +end diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 9f89101d7f7..104a5f50143 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -16,13 +16,26 @@ describe UsersController do context 'with rendered views' do render_views - it 'renders the show template' do - sign_in(user) + describe 'when logged in' do + before do + sign_in(user) + end - get :show, username: user.username + it 'renders the show template' do + get :show, username: user.username - expect(response).to be_success - expect(response).to render_template('show') + expect(response).to be_success + expect(response).to render_template('show') + end + end + + describe 'when logged out' do + it 'renders the show template' do + get :show, username: user.username + + expect(response).to be_success + expect(response).to render_template('show') + end end end end diff --git a/spec/factories.rb b/spec/factories.rb index 200f18f660d..4bf93adabe2 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -165,6 +165,18 @@ FactoryGirl.define do title content file_name + + trait :public do + visibility_level Gitlab::VisibilityLevel::PUBLIC + end + + trait :internal do + visibility_level Gitlab::VisibilityLevel::INTERNAL + end + + trait :private do + visibility_level Gitlab::VisibilityLevel::PRIVATE + end end factory :snippet do diff --git a/spec/factories/ci/commits.rb b/spec/factories/ci/commits.rb index 79e000b7ccb..70e3fa319c6 100644 --- a/spec/factories/ci/commits.rb +++ b/spec/factories/ci/commits.rb @@ -2,17 +2,18 @@ # # Table name: commits # -# id :integer not null, primary key -# project_id :integer -# ref :string(255) -# sha :string(255) -# before_sha :string(255) -# push_data :text -# created_at :datetime -# updated_at :datetime -# tag :boolean default(FALSE) -# yaml_errors :text -# committed_at :datetime +# id :integer not null, primary key +# project_id :integer +# ref :string(255) +# sha :string(255) +# before_sha :string(255) +# push_data :text +# created_at :datetime +# updated_at :datetime +# tag :boolean default(FALSE) +# yaml_errors :text +# committed_at :datetime +# gl_project_id :integer # # Read about factories at https://github.com/thoughtbot/factory_girl diff --git a/spec/factories/ci/projects.rb b/spec/factories/ci/projects.rb index 1183a190353..11cb8c9eeaa 100644 --- a/spec/factories/ci/projects.rb +++ b/spec/factories/ci/projects.rb @@ -31,16 +31,20 @@ FactoryGirl.define do factory :ci_project_without_token, class: Ci::Project do default_ref 'master' - gl_project factory: :empty_project - shared_runners_enabled false factory :ci_project do token 'iPWx6WM4lhHNedGfBpPJNP' end - factory :ci_public_project do - public true + initialize_with do + # TODO: + # this is required, because builds_enabled is initialized when Project is created + # and this create gitlab_ci_project if builds is set to true + # here we take created gitlab_ci_project and update it's attributes + ci_project = create(:empty_project).ensure_gitlab_ci_project + ci_project.update_attributes(attributes) + ci_project end end end diff --git a/spec/factories/commit_statuses.rb b/spec/factories/commit_statuses.rb index 52de437052d..8898b71e2a3 100644 --- a/spec/factories/commit_statuses.rb +++ b/spec/factories/commit_statuses.rb @@ -5,7 +5,7 @@ FactoryGirl.define do name 'default' status 'success' description 'commit status' - commit factory: :ci_commit + commit factory: :ci_commit_with_one_job factory :generic_commit_status, class: GenericCommitStatus do name 'generic' diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb index 6829387c660..8b12ee11af5 100644 --- a/spec/factories/labels.rb +++ b/spec/factories/labels.rb @@ -8,6 +8,7 @@ # project_id :integer # created_at :datetime # updated_at :datetime +# template :boolean default(FALSE) # # Read about factories at https://github.com/thoughtbot/factory_girl diff --git a/spec/factories/lfs_objects.rb b/spec/factories/lfs_objects.rb new file mode 100644 index 00000000000..2da107ba24b --- /dev/null +++ b/spec/factories/lfs_objects.rb @@ -0,0 +1,24 @@ +# == Schema Information +# +# Table name: lfs_objects +# +# id :integer not null, primary key +# oid :string(255) not null +# size :integer not null +# created_at :datetime +# updated_at :datetime +# file :string(255) +# + +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :lfs_object do + oid "b68143e6463773b1b6c6fd009a76c32aeec041faff32ba2ed42fd7f708a17f80" + size 499013 + end + + trait :with_file do + file { fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "`/png") } + end +end diff --git a/spec/factories/lfs_objects_projects.rb b/spec/factories/lfs_objects_projects.rb new file mode 100644 index 00000000000..3772236a77a --- /dev/null +++ b/spec/factories/lfs_objects_projects.rb @@ -0,0 +1,19 @@ +# == Schema Information +# +# Table name: lfs_objects_projects +# +# id :integer not null, primary key +# lfs_object_id :integer not null +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# + +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :lfs_objects_project do + lfs_object + project + end +end diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index 6080d0ccdef..5b4d7f41bc4 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -20,6 +20,7 @@ # position :integer default(0) # locked_at :datetime # updated_by_id :integer +# merge_error :string(255) # FactoryGirl.define do @@ -64,6 +65,11 @@ FactoryGirl.define do target_branch "master" end + trait :merge_when_build_succeeds do + merge_when_build_succeeds true + merge_user author + end + 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/notes.rb b/spec/factories/notes.rb index 9d777ddfccd..35a20adeef3 100644 --- a/spec/factories/notes.rb +++ b/spec/factories/notes.rb @@ -16,6 +16,7 @@ # system :boolean default(FALSE), not null # st_diff :text # updated_by_id :integer +# is_award :boolean default(FALSE), not null # require_relative '../support/repo_helpers' diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 1d500a11ad7..112213377ff 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -28,6 +28,7 @@ # import_type :string(255) # import_source :string(255) # commit_count :integer default(0) +# import_error :text # FactoryGirl.define do diff --git a/spec/factories/releases.rb b/spec/factories/releases.rb index 80d6bbee6c7..43d09b17534 100644 --- a/spec/factories/releases.rb +++ b/spec/factories/releases.rb @@ -1,3 +1,15 @@ +# == Schema Information +# +# Table name: releases +# +# id :integer not null, primary key +# tag :string(255) +# description :text +# project_id :integer +# created_at :datetime +# updated_at :datetime +# + # Read about factories at https://github.com/thoughtbot/factory_girl FactoryGirl.define do diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index c2c7364f6c5..4570e409128 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -87,13 +87,16 @@ describe "Admin::Users", feature: true do end it "should call send mail" do - expect(Notify).to receive(:new_user_email) + expect_any_instance_of(NotificationService).to receive(:new_user) click_button "Create user" end it "should send valid email to user with email & password" do - click_button "Create user" + perform_enqueued_jobs do + click_button "Create user" + end + user = User.find_by(username: 'bang') email = ActionMailer::Base.deliveries.last expect(email.subject).to have_content('Account was created') @@ -111,24 +114,60 @@ describe "Admin::Users", feature: true do expect(page).to have_content(@user.name) end - describe 'Login as another user' do - it 'should show login button for other users and check that it works' do - another_user = create(:user) + describe 'Impersonation' do + let(:another_user) { create(:user) } + before { visit admin_user_path(another_user) } + + context 'before impersonating' do + it 'shows impersonate button for other users' do + expect(page).to have_content('Impersonate') + end + + it 'should not show impersonate button for admin itself' do + visit admin_user_path(@user) + + expect(page).not_to have_content('Impersonate') + end - visit admin_user_path(another_user) + it 'should not show impersonate button for blocked user' do + another_user.block - click_link 'Log in as this user' + visit admin_user_path(another_user) - expect(page).to have_content("Logged in as #{another_user.username}") + expect(page).not_to have_content('Impersonate') - page.within '.sidebar-user .username' do - expect(page).to have_content(another_user.username) + another_user.activate end end - it 'should not show login button for admin itself' do - visit admin_user_path(@user) - expect(page).not_to have_content('Log in as this user') + context 'when impersonating' do + before { click_link 'Impersonate' } + + it 'logs in as the user when impersonate is clicked' do + page.within '.sidebar-user .username' do + expect(page).to have_content(another_user.username) + end + end + + it 'sees impersonation log out icon' do + icon = first('.fa.fa-user-secret') + + expect(icon).to_not eql nil + end + + it 'can log out of impersonated user back to original user' do + find(:css, 'li.impersonation a').click + + page.within '.sidebar-user .username' do + expect(page).to have_content(@user.username) + end + end + + it 'is redirected back to the impersonated users page in the admin after stopping' do + find(:css, 'li.impersonation a').click + + expect(current_path).to eql "/admin/users/#{another_user.username}" + end end end diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb index dc41be8246f..52134556339 100644 --- a/spec/features/atom/users_spec.rb +++ b/spec/features/atom/users_spec.rb @@ -79,6 +79,6 @@ describe "User Feed", feature: true do end def safe_name - html_escape(user.name) + CGI.escapeHTML(user.name) end end diff --git a/spec/features/builds_spec.rb b/spec/features/builds_spec.rb index 158e85e598f..1f99a808f87 100644 --- a/spec/features/builds_spec.rb +++ b/spec/features/builds_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe "Builds" do + let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') } + before do login_as(:user) @commit = FactoryGirl.create :ci_commit @@ -17,7 +19,7 @@ describe "Builds" do end it { expect(page).to have_content 'Running' } - it { expect(page).to have_content 'Cancel all' } + it { expect(page).to have_content 'Cancel running' } it { expect(page).to have_content @build.short_sha } it { expect(page).to have_content @build.ref } it { expect(page).to have_content @build.name } @@ -30,7 +32,7 @@ describe "Builds" do end it { expect(page).to have_content 'No builds to show' } - it { expect(page).to have_content 'Cancel all' } + it { expect(page).to have_content 'Cancel running' } end context "All builds" do @@ -43,7 +45,7 @@ describe "Builds" do it { expect(page).to have_content @build.short_sha } it { expect(page).to have_content @build.ref } it { expect(page).to have_content @build.name } - it { expect(page).to_not have_content 'Cancel all' } + it { expect(page).to_not have_content 'Cancel running' } end end @@ -51,11 +53,11 @@ describe "Builds" do before do @build.run! visit namespace_project_builds_path(@gl_project.namespace, @gl_project) - click_link "Cancel all" + click_link "Cancel running" end it { expect(page).to have_content 'No builds to show' } - it { expect(page).to_not have_content 'Cancel all' } + it { expect(page).to_not have_content 'Cancel running' } end describe "GET /:project/builds/:id" do @@ -66,6 +68,15 @@ describe "Builds" do it { expect(page).to have_content @commit.sha[0..7] } it { expect(page).to have_content @commit.git_commit_message } it { expect(page).to have_content @commit.git_author_name } + + context "Download artifacts" do + before do + @build.update_attributes(artifacts_file: artifacts_file) + visit namespace_project_build_path(@gl_project.namespace, @gl_project, @build) + end + + it { expect(page).to have_content 'Download artifacts' } + end end describe "POST /:project/builds/:id/cancel" do @@ -90,4 +101,14 @@ describe "Builds" do it { expect(page).to have_content 'pending' } it { expect(page).to have_content 'Cancel' } end + + describe "GET /:project/builds/:id/download" do + before do + @build.update_attributes(artifacts_file: artifacts_file) + visit namespace_project_build_path(@gl_project.namespace, @gl_project, @build) + click_link 'Download artifacts' + end + + it { expect(page.response_headers['Content-Type']).to eq(artifacts_file.content_type) } + end end diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb index 340924fafe7..90739cd6a28 100644 --- a/spec/features/commits_spec.rb +++ b/spec/features/commits_spec.rb @@ -19,7 +19,7 @@ describe "Commits" do stub_ci_commit_to_return_yaml_file end - describe "GET /:project/commits/:sha" do + describe "GET /:project/commits/:sha/ci" do before do visit ci_status_path(@commit) end @@ -29,6 +29,20 @@ describe "Commits" do it { expect(page).to have_content @commit.git_author_name } end + context "Download artifacts" do + let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') } + + before do + @build.update_attributes(artifacts_file: artifacts_file) + end + + it do + visit ci_status_path(@commit) + click_on "Download artifacts" + expect(page.response_headers['Content-Type']).to eq(artifacts_file.content_type) + end + end + describe "Cancel all builds" do it "cancels commit" do visit ci_status_path(@commit) diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 32fd4065bb4..3d6d87e764a 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -61,7 +61,7 @@ describe 'Issues', feature: true do it 'allows user to select unasigned', js: true do visit edit_namespace_project_issue_path(project.namespace, project, issue) - expect(page).to have_content "Assign to #{@user.name}" + expect(page).to have_content "Assignee #{@user.name}" first('#s2id_issue_assignee_id').click sleep 2 # wait for ajax stuff to complete @@ -293,10 +293,10 @@ describe 'Issues', feature: true do end def first_issue - page.all('ul.issues-list li').first.text + page.all('ul.issues-list > li').first.text end def last_issue - page.all('ul.issues-list li').last.text + page.all('ul.issues-list > li').last.text end end diff --git a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb new file mode 100644 index 00000000000..a674124aab7 --- /dev/null +++ b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb @@ -0,0 +1,85 @@ +require 'spec_helper' + +feature 'Merge When Build Succeeds', feature: true, js: true do + let(:user) { create(:user) } + + let(:project) { create(:project, :public) } + let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user, title: "Bug NS-04") } + + before do + project.team << [user, :master] + project.enable_ci + end + + context "Active build for Merge Request" do + let!(:ci_commit) { create(:ci_commit, gl_project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) } + let!(:ci_build) { create(:ci_build, commit: ci_commit) } + + before do + login_as user + visit_merge_request(merge_request) + end + + it 'displays the Merge When Build Succeeds button' do + expect(page).to have_link "Merge When Build Succeeds" + end + + context "Merge When Build succeeds enabled" do + before do + click_link "Merge When Build Succeeds" + end + + it 'activates Merge When Build Succeeds feature' do + expect(page).to have_link "Cancel Automatic Merge" + + expect(page).to have_content "Set by #{user.name} to be merged automatically when the build succeeds." + expect(page).to have_content "The source branch will not be removed." + + visit_merge_request(merge_request) # Needed to refresh the page + expect(page).to have_content /Enabled an automatic merge when the build for [0-9a-f]{8} succeeds/i + end + end + end + + context 'When it is enabled' do + let(:merge_request) do + create(:merge_request_with_diffs, :simple, source_project: project, author: user, + merge_user: user, title: "MepMep", merge_when_build_succeeds: true) + end + + let!(:ci_commit) { create(:ci_commit, gl_project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) } + let!(:ci_build) { create(:ci_build, commit: ci_commit) } + + before do + login_as user + visit_merge_request(merge_request) + end + + it 'cancels the automatic merge' do + click_link "Cancel Automatic Merge" + + expect(page).to have_link "Merge When Build Succeeds" + + visit_merge_request(merge_request) # Needed to refresh the page + expect(page).to have_content "Canceled the automatic merge" + end + + it "allows the user to remove the source branch" do + expect(page).to have_link "Remove Source Branch When Merged" + + click_link "Remove Source Branch When Merged" + expect(page).to have_content "The source branch will be removed" + end + end + + context 'Build is not active' do + it "should not allow for enabling" do + visit_merge_request(merge_request) + expect(page).not_to have_link "Merge When Build Succeeds" + end + end + + def visit_merge_request(merge_request) + visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request) + end +end diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb index d7cb3b2e86e..f0fc6916c4d 100644 --- a/spec/features/notes_on_merge_requests_spec.rb +++ b/spec/features/notes_on_merge_requests_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe 'Comments', feature: true do include RepoHelpers + include WaitForAjax describe 'On a merge request', js: true, feature: true do let!(:merge_request) { create(:merge_request) } @@ -123,8 +124,8 @@ describe 'Comments', feature: true do it 'removes the attachment div and resets the edit form' do find('.js-note-attachment-delete').click is_expected.not_to have_css('.note-attachment') - expect(find('.current-note-edit-form', visible: false)). - not_to be_visible + is_expected.not_to have_css('.current-note-edit-form') + wait_for_ajax end end end diff --git a/spec/features/password_reset_spec.rb b/spec/features/password_reset_spec.rb index 85e70b4d47f..257d363438c 100644 --- a/spec/features/password_reset_spec.rb +++ b/spec/features/password_reset_spec.rb @@ -3,11 +3,12 @@ require 'spec_helper' feature 'Password reset', feature: true do describe 'throttling' do it 'sends reset instructions when not previously sent' do - visit root_path - forgot_password(create(:user)) + user = create(:user) + forgot_password(user) - expect(page).to have_content(I18n.t('devise.passwords.send_instructions')) + expect(page).to have_content(I18n.t('devise.passwords.send_paranoid_instructions')) expect(current_path).to eq new_user_session_path + expect(user.recently_sent_password_reset?).to be_truthy end it 'sends reset instructions when previously sent more than a minute ago' do @@ -15,26 +16,25 @@ feature 'Password reset', feature: true do user.send_reset_password_instructions user.update_attribute(:reset_password_sent_at, 5.minutes.ago) - visit root_path - forgot_password(user) - - expect(page).to have_content(I18n.t('devise.passwords.send_instructions')) + expect{ forgot_password(user) }.to change{ user.reset_password_sent_at } + expect(page).to have_content(I18n.t('devise.passwords.send_paranoid_instructions')) expect(current_path).to eq new_user_session_path end - it "throttles multiple resets in a short timespan" do + it 'throttles multiple resets in a short timespan' do user = create(:user) user.send_reset_password_instructions + # Reload because PG handles datetime less precisely than Ruby/Rails + user.reload - visit root_path - forgot_password(user) - - expect(page).to have_content(I18n.t('devise.passwords.recently_reset')) - expect(current_path).to eq new_user_password_path + expect{ forgot_password(user) }.not_to change{ user.reset_password_sent_at } + expect(page).to have_content(I18n.t('devise.passwords.send_paranoid_instructions')) + expect(current_path).to eq new_user_session_path end end def forgot_password(user) + visit root_path click_on 'Forgot your password?' fill_in 'Email', with: user.email click_button 'Reset password' diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb index 06adb7633b2..b0259026630 100644 --- a/spec/features/runners_spec.rb +++ b/spec/features/runners_spec.rb @@ -14,15 +14,25 @@ describe "Runners" do @project2 = FactoryGirl.create :ci_project @project2.gl_project.team << [user, :master] + @project3 = FactoryGirl.create :ci_project + @project3.gl_project.team << [user, :developer] + @shared_runner = FactoryGirl.create :ci_shared_runner @specific_runner = FactoryGirl.create :ci_specific_runner @specific_runner2 = FactoryGirl.create :ci_specific_runner + @specific_runner3 = FactoryGirl.create :ci_specific_runner @project.runners << @specific_runner @project2.runners << @specific_runner2 + @project3.runners << @specific_runner3 visit runners_path(@project.gl_project) end + before do + expect(page).to_not have_content(@specific_runner3.display_name) + expect(page).to_not have_content(@specific_runner3.display_name) + end + it "places runners in right places" do expect(page.find(".available-specific-runners")).to have_content(@specific_runner2.display_name) expect(page.find(".activated-specific-runners")).to have_content(@specific_runner.display_name) @@ -76,10 +86,10 @@ describe "Runners" do @project.gl_project.team << [user, :master] @specific_runner = FactoryGirl.create :ci_specific_runner @project.runners << @specific_runner - visit runners_path(@project.gl_project) end it "shows runner information" do + visit runners_path(@project.gl_project) click_on @specific_runner.short_sha expect(page).to have_content(@specific_runner.platform) end diff --git a/spec/finders/contributed_projects_finder_spec.rb b/spec/finders/contributed_projects_finder_spec.rb new file mode 100644 index 00000000000..65d7f14c721 --- /dev/null +++ b/spec/finders/contributed_projects_finder_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe ContributedProjectsFinder do + let(:source_user) { create(:user) } + let(:current_user) { create(:user) } + + let(:finder) { described_class.new(source_user) } + + let!(:public_project) { create(:project, :public) } + let!(:private_project) { create(:project, :private) } + + before do + private_project.team << [source_user, Gitlab::Access::MASTER] + private_project.team << [current_user, Gitlab::Access::DEVELOPER] + public_project.team << [source_user, Gitlab::Access::MASTER] + + create(:event, action: Event::PUSHED, project: public_project, + target: public_project, author: source_user) + + create(:event, action: Event::PUSHED, project: private_project, + target: private_project, author: source_user) + end + + describe 'without a current user' do + subject { finder.execute } + + it { is_expected.to eq([public_project]) } + end + + describe 'with a current user' do + subject { finder.execute(current_user) } + + it { is_expected.to eq([private_project, public_project]) } + end +end diff --git a/spec/finders/group_finder_spec.rb b/spec/finders/group_finder_spec.rb deleted file mode 100644 index 78dc027837c..00000000000 --- a/spec/finders/group_finder_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'spec_helper' - -describe GroupsFinder do - let(:user) { create :user } - let!(:group) { create :group } - let!(:public_group) { create :group, public: true } - - describe :execute do - it 'finds public group' do - groups = GroupsFinder.new.execute(user) - expect(groups.size).to eq(1) - expect(groups.first).to eq(public_group) - end - end -end diff --git a/spec/finders/groups_finder_spec.rb b/spec/finders/groups_finder_spec.rb new file mode 100644 index 00000000000..4f6a000822e --- /dev/null +++ b/spec/finders/groups_finder_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' + +describe GroupsFinder do + describe '#execute' do + let(:user) { create(:user) } + + let(:group1) { create(:group) } + let(:group2) { create(:group) } + let(:group3) { create(:group) } + let(:group4) { create(:group, public: true) } + + let!(:public_project) { create(:project, :public, group: group1) } + let!(:internal_project) { create(:project, :internal, group: group2) } + let!(:private_project) { create(:project, :private, group: group3) } + + let(:finder) { described_class.new } + + describe 'with a user' do + subject { finder.execute(user) } + + describe 'when the user is not a member of any groups' do + it { is_expected.to eq([group4, group2, group1]) } + end + + describe 'when the user is a member of a group' do + before do + group3.add_user(user, Gitlab::Access::DEVELOPER) + end + + it { is_expected.to eq([group4, group3, group2, group1]) } + end + + describe 'when the user is a member of a private project' do + before do + private_project.team.add_user(user, Gitlab::Access::DEVELOPER) + end + + it { is_expected.to eq([group4, group3, group2, group1]) } + end + end + + describe 'without a user' do + subject { finder.execute } + + it { is_expected.to eq([group4, group1]) } + end + end +end diff --git a/spec/finders/joined_groups_finder_spec.rb b/spec/finders/joined_groups_finder_spec.rb new file mode 100644 index 00000000000..2d9068cc720 --- /dev/null +++ b/spec/finders/joined_groups_finder_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe JoinedGroupsFinder do + describe '#execute' do + let(:source_user) { create(:user) } + let(:current_user) { create(:user) } + + let(:group1) { create(:group) } + let(:group2) { create(:group) } + let(:group3) { create(:group) } + let(:group4) { create(:group, public: true) } + + let!(:public_project) { create(:project, :public, group: group1) } + let!(:internal_project) { create(:project, :internal, group: group2) } + let!(:private_project) { create(:project, :private, group: group3) } + + let(:finder) { described_class.new(source_user) } + + before do + [group1, group2, group3, group4].each do |group| + group.add_user(source_user, Gitlab::Access::MASTER) + end + end + + describe 'with a current user' do + describe 'when the current user has access to the projects of the source user' do + before do + private_project.team.add_user(current_user, Gitlab::Access::DEVELOPER) + end + + subject { finder.execute(current_user) } + + it { is_expected.to eq([group4, group3, group2, group1]) } + end + + describe 'when the current user does not have access to the projects of the source user' do + subject { finder.execute(current_user) } + + it { is_expected.to eq([group4, group2, group1]) } + end + end + + describe 'without a current user' do + subject { finder.execute } + + it { is_expected.to eq([group4, group1]) } + end + end +end diff --git a/spec/finders/personal_projects_finder_spec.rb b/spec/finders/personal_projects_finder_spec.rb new file mode 100644 index 00000000000..38817add456 --- /dev/null +++ b/spec/finders/personal_projects_finder_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe PersonalProjectsFinder do + let(:source_user) { create(:user) } + let(:current_user) { create(:user) } + + let(:finder) { described_class.new(source_user) } + + let!(:public_project) do + create(:project, :public, namespace: source_user.namespace, name: 'A', + path: 'A') + end + + let!(:private_project) do + create(:project, :private, namespace: source_user.namespace, name: 'B', + path: 'B') + end + + before do + private_project.team << [current_user, Gitlab::Access::DEVELOPER] + end + + describe 'without a current user' do + subject { finder.execute } + + it { is_expected.to eq([public_project]) } + end + + describe 'with a current user' do + subject { finder.execute(current_user) } + + it { is_expected.to eq([private_project, public_project]) } + end +end diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb index de9d4cd6128..f32641ef0f6 100644 --- a/spec/finders/projects_finder_spec.rb +++ b/spec/finders/projects_finder_spec.rb @@ -1,51 +1,63 @@ require 'spec_helper' describe ProjectsFinder do - let(:user) { create :user } - let(:group) { create :group } + describe '#execute' do + let(:user) { create(:user) } + let(:group) { create(:group) } - let(:project1) { create(:empty_project, :public, group: group) } - let(:project2) { create(:empty_project, :internal, group: group) } - let(:project3) { create(:empty_project, :private, group: group) } - let(:project4) { create(:empty_project, :private, group: group) } + let!(:private_project) do + create(:project, :private, name: 'A', path: 'A') + end - context 'non authenticated' do - subject { ProjectsFinder.new.execute(nil, group: group) } + let!(:internal_project) do + create(:project, :internal, group: group, name: 'B', path: 'B') + end - it { is_expected.to include(project1) } - it { is_expected.not_to include(project2) } - it { is_expected.not_to include(project3) } - it { is_expected.not_to include(project4) } - end + let!(:public_project) do + create(:project, :public, group: group, name: 'C', path: 'C') + end - context 'authenticated' do - subject { ProjectsFinder.new.execute(user, group: group) } + let(:finder) { described_class.new } - it { is_expected.to include(project1) } - it { is_expected.to include(project2) } - it { is_expected.not_to include(project3) } - it { is_expected.not_to include(project4) } - end + describe 'without a group' do + describe 'without a user' do + subject { finder.execute } - context 'authenticated, project member' do - before { project3.team << [user, :developer] } + it { is_expected.to eq([public_project]) } + end - subject { ProjectsFinder.new.execute(user, group: group) } + describe 'with a user' do + subject { finder.execute(user) } - it { is_expected.to include(project1) } - it { is_expected.to include(project2) } - it { is_expected.to include(project3) } - it { is_expected.not_to include(project4) } - end + describe 'without private projects' do + it { is_expected.to eq([public_project, internal_project]) } + end + + describe 'with private projects' do + before do + private_project.team.add_user(user, Gitlab::Access::MASTER) + end + + it do + is_expected.to eq([public_project, internal_project, + private_project]) + end + end + end + end + + describe 'with a group' do + describe 'without a user' do + subject { finder.execute(nil, group: group) } - context 'authenticated, group member' do - before { group.add_developer(user) } + it { is_expected.to eq([public_project]) } + end - subject { ProjectsFinder.new.execute(user, group: group) } + describe 'with a user' do + subject { finder.execute(user, group: group) } - it { is_expected.to include(project1) } - it { is_expected.to include(project2) } - it { is_expected.to include(project3) } - it { is_expected.to include(project4) } + it { is_expected.to eq([public_project, internal_project]) } + end + end end end diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb index 41d12afa9ce..e8dfc5c0eb1 100644 --- a/spec/fixtures/markdown.md.erb +++ b/spec/fixtures/markdown.md.erb @@ -153,6 +153,7 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e - Ignores invalid: <%= User.reference_prefix %>fake_user - Ignored in code: `<%= user.to_reference %>` - Ignored in links: [Link to <%= user.to_reference %>](#user-link) +- Link to user by reference: [User](<%= user.to_reference %>) #### IssueReferenceFilter @@ -160,6 +161,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e - Issue in another project: <%= xissue.to_reference(project) %> - Ignored in code: `<%= issue.to_reference %>` - Ignored in links: [Link to <%= issue.to_reference %>](#issue-link) +- Issue by URL: <%= urls.namespace_project_issue_url(issue.project.namespace, issue.project, issue) %> +- Link to issue by reference: [Issue](<%= issue.to_reference %>) +- Link to issue by URL: [Issue](<%= urls.namespace_project_issue_url(issue.project.namespace, issue.project, issue) %>) #### MergeRequestReferenceFilter @@ -167,6 +171,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e - Merge request in another project: <%= xmerge_request.to_reference(project) %> - Ignored in code: `<%= merge_request.to_reference %>` - Ignored in links: [Link to <%= merge_request.to_reference %>](#merge-request-link) +- Merge request by URL: <%= urls.namespace_project_merge_request_url(merge_request.project.namespace, merge_request.project, merge_request) %> +- Link to merge request by reference: [Merge request](<%= merge_request.to_reference %>) +- Link to merge request by URL: [Merge request](<%= urls.namespace_project_merge_request_url(merge_request.project.namespace, merge_request.project, merge_request) %>) #### SnippetReferenceFilter @@ -174,6 +181,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e - Snippet in another project: <%= xsnippet.to_reference(project) %> - Ignored in code: `<%= snippet.to_reference %>` - Ignored in links: [Link to <%= snippet.to_reference %>](#snippet-link) +- Snippet by URL: <%= urls.namespace_project_snippet_url(snippet.project.namespace, snippet.project, snippet) %> +- Link to snippet by reference: [Snippet](<%= snippet.to_reference %>) +- Link to snippet by URL: [Snippet](<%= urls.namespace_project_snippet_url(snippet.project.namespace, snippet.project, snippet) %>) #### CommitRangeReferenceFilter @@ -181,6 +191,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e - Range in another project: <%= xcommit_range.to_reference(project) %> - Ignored in code: `<%= commit_range.to_reference %>` - Ignored in links: [Link to <%= commit_range.to_reference %>](#commit-range-link) +- Range by URL: <%= urls.namespace_project_compare_url(commit_range.project.namespace, commit_range.project, commit_range.to_param) %> +- Link to range by reference: [Range](<%= commit_range.to_reference %>) +- Link to range by URL: [Range](<%= urls.namespace_project_compare_url(commit_range.project.namespace, commit_range.project, commit_range.to_param) %>) #### CommitReferenceFilter @@ -188,6 +201,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e - Commit in another project: <%= xcommit.to_reference(project) %> - Ignored in code: `<%= commit.to_reference %>` - Ignored in links: [Link to <%= commit.to_reference %>](#commit-link) +- Commit by URL: <%= urls.namespace_project_commit_url(commit.project.namespace, commit.project, commit) %> +- Link to commit by reference: [Commit](<%= commit.to_reference %>) +- Link to commit by URL: [Commit](<%= urls.namespace_project_commit_url(commit.project.namespace, commit.project, commit) %>) #### LabelReferenceFilter @@ -196,6 +212,7 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e - Label by name in quotes: <%= label.to_reference(:name) %> - Ignored in code: `<%= simple_label.to_reference %>` - Ignored in links: [Link to <%= simple_label.to_reference %>](#label-link) +- Link to label by reference: [Label](<%= label.to_reference %>) ### Task Lists diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 1dfae0fbd3f..5568f06639c 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -59,7 +59,7 @@ describe ApplicationHelper do avatar_url = "http://localhost/uploads/project/avatar/#{project.id}/banana_sample.gif" expect(helper.project_icon("#{project.namespace.to_param}/#{project.to_param}").to_s). - to eq "<img alt=\"Banana sample\" src=\"#{avatar_url}\" />" + to eq "<img src=\"#{avatar_url}\" alt=\"Banana sample\" />" end it 'should give uploaded icon when present' do @@ -95,9 +95,9 @@ describe ApplicationHelper do end it 'should call gravatar_icon when no User exists with the given email' do - expect(helper).to receive(:gravatar_icon).with('foo@example.com', 20) + expect(helper).to receive(:gravatar_icon).with('foo@example.com', 20, 2) - helper.avatar_icon('foo@example.com', 20) + helper.avatar_icon('foo@example.com', 20, 2) end describe 'using a User' do @@ -150,15 +150,19 @@ describe ApplicationHelper do stub_gravatar_setting(plain_url: 'http://example.local/?s=%{size}&hash=%{hash}') expect(gravatar_icon(user_email, 20)). - to eq('http://example.local/?s=20&hash=b58c6f14d292556214bd64909bcdb118') + to eq('http://example.local/?s=40&hash=b58c6f14d292556214bd64909bcdb118') end it 'accepts a custom size argument' do - expect(helper.gravatar_icon(user_email, 64)).to include '?s=64' + expect(helper.gravatar_icon(user_email, 64)).to include '?s=128' end - it 'defaults size to 40 when given an invalid size' do - expect(helper.gravatar_icon(user_email, nil)).to include '?s=40' + it 'defaults size to 40@2x when given an invalid size' do + expect(helper.gravatar_icon(user_email, nil)).to include '?s=80' + end + + it 'accepts a scaling factor' do + expect(helper.gravatar_icon(user_email, 40, 3)).to include '?s=120' end it 'ignores case and surrounding whitespace' do @@ -274,7 +278,7 @@ describe ApplicationHelper do el = element.next_element expect(el.name).to eq 'script' - expect(el.text).to include "$('.js-timeago').timeago()" + expect(el.text).to include "$('.js-timeago').last().timeago()" end it 'allows the script tag to be excluded' do diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index 78a6b631eb2..1f2c4ee77b5 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -127,4 +127,30 @@ describe IssuesHelper do it { is_expected.to eq("!1, !2, or !3") } end + describe "#url_to_emoji" do + it "returns url" do + expect(url_to_emoji("smile")).to include("emoji/1F604.png") + end + end + + describe "#emoji_list" do + it "returns url" do + expect(emoji_list).to be_kind_of(Array) + end + end + + describe "#note_active_class" do + before do + @note = create :note + @note1 = create :note + end + + it "returns empty string for unauthenticated user" do + expect(note_active_class(Note.all, nil)).to eq("") + end + + it "returns active string for author" do + expect(note_active_class(Note.all, @note.author)).to eq("active") + end + end end diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb index c4f7693329c..aafc24397a9 100644 --- a/spec/helpers/visibility_level_helper_spec.rb +++ b/spec/helpers/visibility_level_helper_spec.rb @@ -7,69 +7,52 @@ describe VisibilityLevelHelper do init_haml_helpers end - let(:project) { create(:project) } + let(:project) { build(:project) } + let(:personal_snippet) { build(:personal_snippet) } + let(:project_snippet) { build(:project_snippet) } describe 'visibility_level_description' do - shared_examples 'a visibility level description' do - let(:desc) do - visibility_level_description(Gitlab::VisibilityLevel::PRIVATE, - form_model) - end - - let(:expected_class) do - class_name = case form_model.class.name - when 'String' - form_model - else - form_model.class.name - end - - class_name.match(/(project|snippet)$/i)[0] - end - - it 'should refer to the correct class' do - expect(desc).to match(/#{expected_class}/i) + context 'used with a Project' do + it 'delegates projects to #project_visibility_level_description' do + expect(visibility_level_description(Gitlab::VisibilityLevel::PRIVATE, project)) + .to match /project/i end end - context 'form_model argument is a String' do - context 'model object is a personal snippet' do - it_behaves_like 'a visibility level description' do - let(:form_model) { 'PersonalSnippet' } - end + context 'called with a Snippet' do + it 'delegates snippets to #snippet_visibility_level_description' do + expect(visibility_level_description(Gitlab::VisibilityLevel::INTERNAL, project_snippet)) + .to match /snippet/i end + end + end - context 'model object is a project snippet' do - it_behaves_like 'a visibility level description' do - let(:form_model) { 'ProjectSnippet' } - end - end + describe "#project_visibility_level_description" do + it "describes private projects" do + expect(project_visibility_level_description(Gitlab::VisibilityLevel::PRIVATE)) + .to eq "Project access must be granted explicitly to each user." + end - context 'model object is a project' do - it_behaves_like 'a visibility level description' do - let(:form_model) { 'Project' } - end - end + it "describes public projects" do + expect(project_visibility_level_description(Gitlab::VisibilityLevel::PUBLIC)) + .to eq "The project can be cloned without any authentication." end + end - context 'form_model argument is a model object' do - context 'model object is a personal snippet' do - it_behaves_like 'a visibility level description' do - let(:form_model) { create(:personal_snippet) } - end - end + describe "#snippet_visibility_level_description" do + it 'describes visibility only for me' do + expect(snippet_visibility_level_description(Gitlab::VisibilityLevel::PRIVATE, personal_snippet)) + .to eq "The snippet is visible only to me." + end - context 'model object is a project snippet' do - it_behaves_like 'a visibility level description' do - let(:form_model) { create(:project_snippet, project: project) } - end - end + it 'describes visibility for project members' do + expect(snippet_visibility_level_description(Gitlab::VisibilityLevel::PRIVATE, project_snippet)) + .to eq "The snippet is visible only to project members." + end - context 'model object is a project' do - it_behaves_like 'a visibility level description' do - let(:form_model) { project } - end - end + it 'defaults to personal snippet' do + expect(snippet_visibility_level_description(Gitlab::VisibilityLevel::PRIVATE)) + .to eq "The snippet is visible only to me." end end diff --git a/spec/javascripts/fixtures/merge_request_tabs.html.haml b/spec/javascripts/fixtures/merge_request_tabs.html.haml index 7624a713948..68678c3d7e3 100644 --- a/spec/javascripts/fixtures/merge_request_tabs.html.haml +++ b/spec/javascripts/fixtures/merge_request_tabs.html.haml @@ -1,12 +1,12 @@ %ul.nav.nav-tabs.merge-request-tabs %li.notes-tab - %a{href: '/foo/bar/merge_requests/1', data: {target: '#notes', action: 'notes', toggle: 'tab'}} + %a{href: '/foo/bar/merge_requests/1', data: {target: 'div#notes', action: 'notes', toggle: 'tab'}} Discussion %li.commits-tab - %a{href: '/foo/bar/merge_requests/1/commits', data: {target: '#commits', action: 'commits', toggle: 'tab'}} + %a{href: '/foo/bar/merge_requests/1/commits', data: {target: 'div#commits', action: 'commits', toggle: 'tab'}} Commits %li.diffs-tab - %a{href: '/foo/bar/merge_requests/1/diffs', data: {target: '#diffs', action: 'diffs', toggle: 'tab'}} + %a{href: '/foo/bar/merge_requests/1/diffs', data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'}} Diffs .tab-content diff --git a/spec/lib/ci/ansi2html_spec.rb b/spec/lib/ci/ansi2html_spec.rb index 75c023bbc43..3a2b568f4c7 100644 --- a/spec/lib/ci/ansi2html_spec.rb +++ b/spec/lib/ci/ansi2html_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Ci::Ansi2html do +describe Ci::Ansi2html, lib: true do subject { Ci::Ansi2html } it "prints non-ansi as-is" do diff --git a/spec/lib/ci/charts_spec.rb b/spec/lib/ci/charts_spec.rb index 83e2ad220b8..50a77308cde 100644 --- a/spec/lib/ci/charts_spec.rb +++ b/spec/lib/ci/charts_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe "Charts" do +describe Ci::Charts, lib: true do context "build_times" do before do diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 9963f76f993..ce4a5244bd0 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' module Ci - describe GitlabCiYamlProcessor do + describe GitlabCiYamlProcessor, lib: true do let(:path) { 'path' } describe "#builds_for_ref" do @@ -333,9 +333,104 @@ module Ci end end + describe "Caches" do + it "returns cache when defined globally" do + config = YAML.dump({ + cache: { paths: ["logs/", "binaries/"], untracked: true }, + rspec: { + script: "rspec" + } + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1) + expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq( + paths: ["logs/", "binaries/"], + untracked: true, + ) + end + + it "returns cache when defined in a job" do + config = YAML.dump({ + rspec: { + cache: { paths: ["logs/", "binaries/"], untracked: true }, + script: "rspec" + } + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1) + expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq( + paths: ["logs/", "binaries/"], + untracked: true, + ) + end + + it "overwrite cache when defined for a job and globally" do + config = YAML.dump({ + cache: { paths: ["logs/", "binaries/"], untracked: true }, + rspec: { + script: "rspec", + cache: { paths: ["test/"], untracked: false }, + } + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1) + expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq( + paths: ["test/"], + untracked: false, + ) + end + end + + describe "Artifacts" do + it "returns artifacts when defined" do + config = YAML.dump({ + image: "ruby:2.1", + services: ["mysql"], + before_script: ["pwd"], + rspec: { + artifacts: { paths: ["logs/", "binaries/"], untracked: true }, + script: "rspec" + } + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1) + expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({ + except: nil, + stage: "test", + stage_idx: 1, + name: :rspec, + only: nil, + commands: "pwd\nrspec", + tag_list: [], + options: { + image: "ruby:2.1", + services: ["mysql"], + artifacts: { + paths: ["logs/", "binaries/"], + untracked: true + } + }, + 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) + end + it "indicates that object is invalid" do - expect{GitlabCiYamlProcessor.new("invalid_yaml\n!ccdvlf%612334@@@@")}.to raise_error(GitlabCiYamlProcessor::ValidationError) + expect{GitlabCiYamlProcessor.new("invalid_yaml")}.to raise_error(GitlabCiYamlProcessor::ValidationError) end it "returns errors if tags parameter is invalid" do @@ -491,6 +586,48 @@ module Ci GitlabCiYamlProcessor.new(config, path) 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:untracked is not an array of strings" do + config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { untracked: "string" } } }) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:untracked parameter should be an boolean") + end + + it "returns errors if job artifacts:paths is not an array of strings" do + config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { paths: "string" } } }) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:paths parameter should be an array of strings") + end + + it "returns errors if cache:untracked is not an array of strings" do + config = YAML.dump({ cache: { untracked: "string" }, rspec: { script: "test" } }) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "cache:untracked parameter should be an boolean") + end + + it "returns errors if cache:paths is not an array of strings" do + config = YAML.dump({ cache: { paths: "string" }, rspec: { script: "test" } }) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "cache:paths parameter should be an array of strings") + end + + it "returns errors if job cache:untracked is not an array of strings" do + config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", cache: { untracked: "string" } } }) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: cache:untracked parameter should be an boolean") + end + + it "returns errors if job cache:paths is not an array of strings" do + config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", cache: { paths: "string" } } }) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: cache:paths parameter should be an array of strings") + end end end end diff --git a/spec/lib/disable_email_interceptor_spec.rb b/spec/lib/disable_email_interceptor_spec.rb index a9624e9a2b7..c2a7b20b84d 100644 --- a/spec/lib/disable_email_interceptor_spec.rb +++ b/spec/lib/disable_email_interceptor_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe DisableEmailInterceptor do +describe DisableEmailInterceptor, lib: true do before do ActionMailer::Base.register_interceptor(DisableEmailInterceptor) end diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb index 48bc60eed16..f38fadda9ba 100644 --- a/spec/lib/extracts_path_spec.rb +++ b/spec/lib/extracts_path_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe ExtractsPath do +describe ExtractsPath, lib: true do include ExtractsPath include RepoHelpers include Gitlab::Application.routes.url_helpers diff --git a/spec/lib/file_size_validator_spec.rb b/spec/lib/file_size_validator_spec.rb index 12ccc051c74..fda6f9a6c88 100644 --- a/spec/lib/file_size_validator_spec.rb +++ b/spec/lib/file_size_validator_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Gitlab::FileSizeValidatorSpec' do +describe FileSizeValidator, lib: true do let(:validator) { FileSizeValidator.new(options) } let(:attachment) { AttachmentUploader.new } let(:note) { create(:note) } diff --git a/spec/lib/git_ref_validator_spec.rb b/spec/lib/git_ref_validator_spec.rb index 4633b6f3934..dc57e94f193 100644 --- a/spec/lib/git_ref_validator_spec.rb +++ b/spec/lib/git_ref_validator_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::GitRefValidator do +describe Gitlab::GitRefValidator, lib: true do it { expect(Gitlab::GitRefValidator.validate('feature/new')).to be_truthy } it { expect(Gitlab::GitRefValidator.validate('implement_@all')).to be_truthy } it { expect(Gitlab::GitRefValidator.validate('my_new_feature')).to be_truthy } diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb index 03e36fd3552..3a860899e18 100644 --- a/spec/lib/gitlab/asciidoc_spec.rb +++ b/spec/lib/gitlab/asciidoc_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' require 'nokogiri' module Gitlab - describe Asciidoc do + describe Asciidoc, lib: true do let(:input) { '<b>ascii</b>' } let(:context) { {} } @@ -50,9 +50,9 @@ module Gitlab filtered_html = '<b>ASCII</b>' allow(Asciidoctor).to receive(:convert).and_return(html) - expect_any_instance_of(HTML::Pipeline).to receive(:call) - .with(html, context) - .and_return(output: Nokogiri::HTML.fragment(filtered_html)) + expect(Gitlab::Markdown).to receive(:render) + .with(html, context.merge(pipeline: :asciidoc)) + .and_return(filtered_html) expect( render('foo', context) ).to eql filtered_html end diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index 72806bebe1f..aad291c03cd 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Auth do +describe Gitlab::Auth, lib: true do let(:gl_auth) { Gitlab::Auth.new } describe :find do diff --git a/spec/lib/gitlab/backend/grack_auth_spec.rb b/spec/lib/gitlab/backend/grack_auth_spec.rb index dfa0e10318a..73458735ad9 100644 --- a/spec/lib/gitlab/backend/grack_auth_spec.rb +++ b/spec/lib/gitlab/backend/grack_auth_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe Grack::Auth do +describe Grack::Auth, lib: true do let(:user) { create(:user) } let(:project) { create(:project) } diff --git a/spec/lib/gitlab/backend/shell_spec.rb b/spec/lib/gitlab/backend/shell_spec.rb index b60e23454d6..fd869f48b5c 100644 --- a/spec/lib/gitlab/backend/shell_spec.rb +++ b/spec/lib/gitlab/backend/shell_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Shell do +describe Gitlab::Shell, lib: true do let(:project) { double('Project', id: 7, path: 'diaspora') } let(:gitlab_shell) { Gitlab::Shell.new } @@ -16,7 +16,7 @@ describe Gitlab::Shell do it { expect(gitlab_shell.url_to_repo('diaspora')).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + "diaspora.git") } - describe Gitlab::Shell::KeyAdder do + describe Gitlab::Shell::KeyAdder, lib: true do describe '#add_key' do it 'normalizes space characters in the key' do io = spy diff --git a/spec/lib/gitlab/bitbucket_import/client_spec.rb b/spec/lib/gitlab/bitbucket_import/client_spec.rb index dfe58637eee..aa0699f2ebf 100644 --- a/spec/lib/gitlab/bitbucket_import/client_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/client_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::BitbucketImport::Client do +describe Gitlab::BitbucketImport::Client, lib: true do let(:token) { '123456' } let(:secret) { 'secret' } let(:client) { Gitlab::BitbucketImport::Client.new(token, secret) } diff --git a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb index 0e826a319e0..e1c60e07b4d 100644 --- a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::BitbucketImport::ProjectCreator do +describe Gitlab::BitbucketImport::ProjectCreator, lib: true do let(:user) { create(:user) } let(:repo) do { diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb index 21254f778d3..99288da1e43 100644 --- a/spec/lib/gitlab/closing_issue_extractor_spec.rb +++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb @@ -1,12 +1,19 @@ require 'spec_helper' -describe Gitlab::ClosingIssueExtractor do +describe Gitlab::ClosingIssueExtractor, lib: true do let(:project) { create(:project) } + let(:project2) { create(:project) } let(:issue) { create(:issue, project: project) } + let(:issue2) { create(:issue, project: project2) } let(:reference) { issue.to_reference } + let(:cross_reference) { issue2.to_reference(project) } subject { described_class.new(project, project.creator) } + before do + project2.team << [project.creator, :master] + end + describe "#closed_by_message" do context 'with a single reference' do it do @@ -130,6 +137,27 @@ describe Gitlab::ClosingIssueExtractor do end end + context "with a cross-project reference" do + it do + message = "Closes #{cross_reference}" + expect(subject.closed_by_message(message)).to eq([issue2]) + end + end + + context "with a cross-project URL" do + it do + message = "Closes #{urls.namespace_project_issue_url(issue2.project.namespace, issue2.project, issue2)}" + expect(subject.closed_by_message(message)).to eq([issue2]) + end + end + + context "with an invalid URL" do + it do + message = "Closes https://google.com#{urls.namespace_project_issue_path(issue2.project.namespace, issue2.project, issue2)}" + expect(subject.closed_by_message(message)).to eq([]) + end + end + context 'with multiple references' do let(:other_issue) { create(:issue, project: project) } let(:third_issue) { create(:issue, project: project) } @@ -171,6 +199,31 @@ describe Gitlab::ClosingIssueExtractor do expect(subject.closed_by_message(message)). to match_array([issue, other_issue, third_issue]) end + + it "fetches cross-project references" do + message = "Closes #{reference} and #{cross_reference}" + + expect(subject.closed_by_message(message)). + to match_array([issue, issue2]) + end + + it "fetches cross-project URL references" do + message = "Closes #{urls.namespace_project_issue_url(issue2.project.namespace, issue2.project, issue2)} and #{reference}" + + expect(subject.closed_by_message(message)). + to match_array([issue, issue2]) + end + + it "ignores invalid cross-project URL references" do + message = "Closes https://google.com#{urls.namespace_project_issue_path(issue2.project.namespace, issue2.project, issue2)} and #{reference}" + + expect(subject.closed_by_message(message)). + to match_array([issue]) + end end end + + def urls + Gitlab::Application.routes.url_helpers + end end diff --git a/spec/lib/gitlab/color_schemes_spec.rb b/spec/lib/gitlab/color_schemes_spec.rb index c7be45dbcd3..0a1ec66f199 100644 --- a/spec/lib/gitlab/color_schemes_spec.rb +++ b/spec/lib/gitlab/color_schemes_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::ColorSchemes do +describe Gitlab::ColorSchemes, lib: true do describe '.body_classes' do it 'returns a space-separated list of class names' do css = described_class.body_classes diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb index 7cdebdf209a..8461e8ce50d 100644 --- a/spec/lib/gitlab/database_spec.rb +++ b/spec/lib/gitlab/database_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Database do +describe Gitlab::Database, lib: true do # These are just simple smoke tests to check if the methods work (regardless # of what they may return). describe '.mysql?' do diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb index 8b7946f3117..c7cdf8691d6 100644 --- a/spec/lib/gitlab/diff/file_spec.rb +++ b/spec/lib/gitlab/diff/file_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Diff::File do +describe Gitlab::Diff::File, lib: true do include RepoHelpers let(:project) { create(:project) } diff --git a/spec/lib/gitlab/diff/parser_spec.rb b/spec/lib/gitlab/diff/parser_spec.rb index 4d5d1431683..ba577bd28e5 100644 --- a/spec/lib/gitlab/diff/parser_spec.rb +++ b/spec/lib/gitlab/diff/parser_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Diff::Parser do +describe Gitlab::Diff::Parser, lib: true do include RepoHelpers let(:project) { create(:project) } diff --git a/spec/lib/gitlab/email/attachment_uploader_spec.rb b/spec/lib/gitlab/email/attachment_uploader_spec.rb index 8fb432367b6..476a21bf996 100644 --- a/spec/lib/gitlab/email/attachment_uploader_spec.rb +++ b/spec/lib/gitlab/email/attachment_uploader_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe Gitlab::Email::AttachmentUploader do +describe Gitlab::Email::AttachmentUploader, lib: true do describe "#execute" do let(:project) { build(:project) } let(:message_raw) { fixture_file("emails/attachment.eml") } diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb index e470b7cd5f5..b535413bbd4 100644 --- a/spec/lib/gitlab/email/receiver_spec.rb +++ b/spec/lib/gitlab/email/receiver_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe Gitlab::Email::Receiver do +describe Gitlab::Email::Receiver, lib: true do before do stub_incoming_email_setting(enabled: true, address: "reply+%{key}@appmail.adventuretime.ooo") end diff --git a/spec/lib/gitlab/email/reply_parser_spec.rb b/spec/lib/gitlab/email/reply_parser_spec.rb index 7cae1da8050..6f8e9a4be64 100644 --- a/spec/lib/gitlab/email/reply_parser_spec.rb +++ b/spec/lib/gitlab/email/reply_parser_spec.rb @@ -1,7 +1,7 @@ require "spec_helper" # Inspired in great part by Discourse's Email::Receiver -describe Gitlab::Email::ReplyParser do +describe Gitlab::Email::ReplyParser, lib: true do describe '#execute' do def test_parse_body(mail_string) described_class.new(Mail::Message.new(mail_string)).execute diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index c7291689e32..9b3a0e3a75f 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::GitAccess do +describe Gitlab::GitAccess, lib: true do let(:access) { Gitlab::GitAccess.new(actor, project) } let(:project) { create(:project) } let(:user) { create(:user) } diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb index 4cb91094cb3..77ecfce6f17 100644 --- a/spec/lib/gitlab/git_access_wiki_spec.rb +++ b/spec/lib/gitlab/git_access_wiki_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::GitAccessWiki do +describe Gitlab::GitAccessWiki, lib: true do let(:access) { Gitlab::GitAccessWiki.new(user, project) } let(:project) { create(:project) } let(:user) { create(:user) } diff --git a/spec/lib/gitlab/github_import/client_spec.rb b/spec/lib/gitlab/github_import/client_spec.rb index 26618120316..49d8cdf4314 100644 --- a/spec/lib/gitlab/github_import/client_spec.rb +++ b/spec/lib/gitlab/github_import/client_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::GithubImport::Client do +describe Gitlab::GithubImport::Client, lib: true do let(:token) { '123456' } let(:client) { Gitlab::GithubImport::Client.new(token) } diff --git a/spec/lib/gitlab/github_import/project_creator_spec.rb b/spec/lib/gitlab/github_import/project_creator_spec.rb index ca61d3c5234..c93a3ebdaec 100644 --- a/spec/lib/gitlab/github_import/project_creator_spec.rb +++ b/spec/lib/gitlab/github_import/project_creator_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::GithubImport::ProjectCreator do +describe Gitlab::GithubImport::ProjectCreator, lib: true do let(:user) { create(:user) } let(:repo) do OpenStruct.new( diff --git a/spec/lib/gitlab/gitlab_import/client_spec.rb b/spec/lib/gitlab/gitlab_import/client_spec.rb index c511c515474..e6831e7c383 100644 --- a/spec/lib/gitlab/gitlab_import/client_spec.rb +++ b/spec/lib/gitlab/gitlab_import/client_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::GitlabImport::Client do +describe Gitlab::GitlabImport::Client, lib: true do let(:token) { '123456' } let(:client) { Gitlab::GitlabImport::Client.new(token) } diff --git a/spec/lib/gitlab/gitlab_import/project_creator_spec.rb b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb index 2d8923d14bb..483f65cd053 100644 --- a/spec/lib/gitlab/gitlab_import/project_creator_spec.rb +++ b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::GitlabImport::ProjectCreator do +describe Gitlab::GitlabImport::ProjectCreator, lib: true do let(:user) { create(:user) } let(:repo) do { diff --git a/spec/lib/gitlab/gitorious_import/project_creator_spec.rb b/spec/lib/gitlab/gitorious_import/project_creator_spec.rb index c1125ca6357..946712ca38e 100644 --- a/spec/lib/gitlab/gitorious_import/project_creator_spec.rb +++ b/spec/lib/gitlab/gitorious_import/project_creator_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::GitoriousImport::ProjectCreator do +describe Gitlab::GitoriousImport::ProjectCreator, lib: true do let(:user) { create(:user) } let(:repo) { Gitlab::GitoriousImport::Repository.new('foo/bar-baz-qux') } let(:namespace){ create(:group, owner: user) } diff --git a/spec/lib/gitlab/google_code_import/client_spec.rb b/spec/lib/gitlab/google_code_import/client_spec.rb index 37985c062b4..85949ae8dc4 100644 --- a/spec/lib/gitlab/google_code_import/client_spec.rb +++ b/spec/lib/gitlab/google_code_import/client_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe Gitlab::GoogleCodeImport::Client do +describe Gitlab::GoogleCodeImport::Client, lib: true do let(:raw_data) { JSON.parse(fixture_file("GoogleCodeProjectHosting.json")) } subject { described_class.new(raw_data) } diff --git a/spec/lib/gitlab/google_code_import/importer_spec.rb b/spec/lib/gitlab/google_code_import/importer_spec.rb index 65ad7524cc2..647631271e0 100644 --- a/spec/lib/gitlab/google_code_import/importer_spec.rb +++ b/spec/lib/gitlab/google_code_import/importer_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe Gitlab::GoogleCodeImport::Importer do +describe Gitlab::GoogleCodeImport::Importer, lib: true do let(:mapped_user) { create(:user, username: "thilo123") } let(:raw_data) { JSON.parse(fixture_file("GoogleCodeProjectHosting.json")) } let(:client) { Gitlab::GoogleCodeImport::Client.new(raw_data) } diff --git a/spec/lib/gitlab/google_code_import/project_creator_spec.rb b/spec/lib/gitlab/google_code_import/project_creator_spec.rb index 35549b48687..499a896ee76 100644 --- a/spec/lib/gitlab/google_code_import/project_creator_spec.rb +++ b/spec/lib/gitlab/google_code_import/project_creator_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::GoogleCodeImport::ProjectCreator do +describe Gitlab::GoogleCodeImport::ProjectCreator, lib: true do let(:user) { create(:user) } let(:repo) do Gitlab::GoogleCodeImport::Repository.new( diff --git a/spec/lib/gitlab/incoming_email_spec.rb b/spec/lib/gitlab/incoming_email_spec.rb index 5fdb9c723b1..bcdba8d4c12 100644 --- a/spec/lib/gitlab/incoming_email_spec.rb +++ b/spec/lib/gitlab/incoming_email_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe Gitlab::IncomingEmail do +describe Gitlab::IncomingEmail, lib: true do describe "self.enabled?" do context "when reply by email is enabled" do before do diff --git a/spec/lib/gitlab/diff/inline_diff_spec.rb b/spec/lib/gitlab/inline_diff_spec.rb index 2e0a05088cc..c690c195112 100644 --- a/spec/lib/gitlab/diff/inline_diff_spec.rb +++ b/spec/lib/gitlab/inline_diff_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::InlineDiff do +describe Gitlab::InlineDiff, lib: true do describe '#processing' do let(:diff) do <<eos diff --git a/spec/lib/gitlab/key_fingerprint_spec.rb b/spec/lib/gitlab/key_fingerprint_spec.rb index 266eab6e793..d09f51f3bfc 100644 --- a/spec/lib/gitlab/key_fingerprint_spec.rb +++ b/spec/lib/gitlab/key_fingerprint_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe Gitlab::KeyFingerprint do +describe Gitlab::KeyFingerprint, lib: true do let(:key) { "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" } let(:fingerprint) { "3f:a2:ee:de:b5:de:53:c3:aa:2f:9c:45:24:4c:47:7b" } diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb index c38f212b405..a628d0c0157 100644 --- a/spec/lib/gitlab/ldap/access_spec.rb +++ b/spec/lib/gitlab/ldap/access_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::LDAP::Access do +describe Gitlab::LDAP::Access, lib: true do let(:access) { Gitlab::LDAP::Access.new user } let(:user) { create(:omniauth_user) } @@ -13,6 +13,11 @@ describe Gitlab::LDAP::Access do end it { is_expected.to be_falsey } + + it 'should block user in GitLab' do + access.allowed? + expect(user).to be_blocked + end end context 'when the user is found' do diff --git a/spec/lib/gitlab/ldap/adapter_spec.rb b/spec/lib/gitlab/ldap/adapter_spec.rb index 38076602df9..4847b5f3b0e 100644 --- a/spec/lib/gitlab/ldap/adapter_spec.rb +++ b/spec/lib/gitlab/ldap/adapter_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::LDAP::Adapter do +describe Gitlab::LDAP::Adapter, lib: true do let(:adapter) { Gitlab::LDAP::Adapter.new 'ldapmain' } describe '#dn_matches_filter?' do diff --git a/spec/lib/gitlab/ldap/auth_hash_spec.rb b/spec/lib/gitlab/ldap/auth_hash_spec.rb index 7d8268536a4..6a53ed1db64 100644 --- a/spec/lib/gitlab/ldap/auth_hash_spec.rb +++ b/spec/lib/gitlab/ldap/auth_hash_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::LDAP::AuthHash do +describe Gitlab::LDAP::AuthHash, lib: true do let(:auth_hash) do Gitlab::LDAP::AuthHash.new( OmniAuth::AuthHash.new( diff --git a/spec/lib/gitlab/ldap/authentication_spec.rb b/spec/lib/gitlab/ldap/authentication_spec.rb index 6e3de914a45..b8f3290e84c 100644 --- a/spec/lib/gitlab/ldap/authentication_spec.rb +++ b/spec/lib/gitlab/ldap/authentication_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::LDAP::Authentication do +describe Gitlab::LDAP::Authentication, lib: true do let(:user) { create(:omniauth_user, extern_uid: dn) } let(:dn) { 'uid=john,ou=people,dc=example,dc=com' } let(:login) { 'john' } diff --git a/spec/lib/gitlab/ldap/config_spec.rb b/spec/lib/gitlab/ldap/config_spec.rb index 3548d647c84..835853a83a4 100644 --- a/spec/lib/gitlab/ldap/config_spec.rb +++ b/spec/lib/gitlab/ldap/config_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::LDAP::Config do +describe Gitlab::LDAP::Config, lib: true do let(:config) { Gitlab::LDAP::Config.new provider } let(:provider) { 'ldapmain' } diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb index b5b56a34952..3bba5e2efa2 100644 --- a/spec/lib/gitlab/ldap/user_spec.rb +++ b/spec/lib/gitlab/ldap/user_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::LDAP::User do +describe Gitlab::LDAP::User, lib: true do let(:ldap_user) { Gitlab::LDAP::User.new(auth_hash) } let(:gl_user) { ldap_user.gl_user } let(:info) do diff --git a/spec/lib/gitlab/lfs/lfs_router_spec.rb b/spec/lib/gitlab/lfs/lfs_router_spec.rb new file mode 100644 index 00000000000..5852b31ab3a --- /dev/null +++ b/spec/lib/gitlab/lfs/lfs_router_spec.rb @@ -0,0 +1,765 @@ +require 'spec_helper' + +describe Gitlab::Lfs::Router, lib: true do + let(:project) { create(:project) } + let(:public_project) { create(:project, :public) } + let(:forked_project) { fork_project(public_project, user) } + + let(:user) { create(:user) } + let(:user_two) { create(:user) } + let!(:lfs_object) { create(:lfs_object, :with_file) } + + let(:request) { Rack::Request.new(env) } + let(:env) do + { + 'rack.input' => '', + 'REQUEST_METHOD' => 'GET', + } + end + + let(:lfs_router_auth) { new_lfs_router(project, user) } + let(:lfs_router_noauth) { new_lfs_router(project, nil) } + let(:lfs_router_public_auth) { new_lfs_router(public_project, user) } + let(:lfs_router_public_noauth) { new_lfs_router(public_project, nil) } + let(:lfs_router_forked_noauth) { new_lfs_router(forked_project, nil) } + let(:lfs_router_forked_auth) { new_lfs_router(forked_project, user_two) } + + let(:sample_oid) { "b68143e6463773b1b6c6fd009a76c32aeec041faff32ba2ed42fd7f708a17f80" } + let(:sample_size) { 499013 } + let(:respond_with_deprecated) {[ 501, { "Content-Type"=>"application/json; charset=utf-8" }, ["{\"message\":\"Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.\",\"documentation_url\":\"#{Gitlab.config.gitlab.url}/help\"}"]]} + let(:respond_with_disabled) {[ 501, { "Content-Type"=>"application/json; charset=utf-8" }, ["{\"message\":\"Git LFS is not enabled on this GitLab server, contact your admin.\",\"documentation_url\":\"#{Gitlab.config.gitlab.url}/help\"}"]]} + + describe 'when lfs is disabled' do + before do + allow(Gitlab.config.lfs).to receive(:enabled).and_return(false) + env['REQUEST_METHOD'] = 'POST' + body = { + 'objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078 + }, + { 'oid' => sample_oid, + 'size' => sample_size + } + ], + 'operation' => 'upload' + }.to_json + env['rack.input'] = StringIO.new(body) + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/batch" + end + + it 'responds with 501' do + expect(lfs_router_auth.try_call).to match_array(respond_with_disabled) + end + end + + describe 'when fetching lfs object using deprecated API' do + before do + enable_lfs + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/#{sample_oid}" + end + + it 'responds with 501' do + expect(lfs_router_auth.try_call).to match_array(respond_with_deprecated) + end + end + + describe 'when fetching lfs object' do + before do + enable_lfs + env['HTTP_ACCEPT'] = "application/vnd.git-lfs+json; charset=utf-8" + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}" + end + + describe 'and request comes from gitlab-workhorse' do + context 'without user being authorized' do + it "responds with status 401" do + expect(lfs_router_noauth.try_call.first).to eq(401) + end + end + + context 'with required headers' do + before do + env['HTTP_X_SENDFILE_TYPE'] = "X-Sendfile" + end + + context 'when user does not have project access' do + it "responds with status 403" do + expect(lfs_router_auth.try_call.first).to eq(403) + end + end + + context 'when user has project access' do + before do + project.lfs_objects << lfs_object + project.team << [user, :master] + end + + it "responds with status 200" do + expect(lfs_router_auth.try_call.first).to eq(200) + end + + it "responds with the file location" do + expect(lfs_router_auth.try_call[1]['Content-Type']).to eq("application/octet-stream") + expect(lfs_router_auth.try_call[1]['X-Sendfile']).to eq(lfs_object.file.path) + end + end + end + + context 'without required headers' do + it "responds with status 403" do + expect(lfs_router_auth.try_call.first).to eq(403) + end + end + end + end + + describe 'when handling lfs request using deprecated API' do + before do + enable_lfs + env['REQUEST_METHOD'] = 'POST' + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects" + end + + it 'responds with 501' do + expect(lfs_router_auth.try_call).to match_array(respond_with_deprecated) + end + end + + describe 'when handling lfs batch request' do + before do + enable_lfs + env['REQUEST_METHOD'] = 'POST' + env['PATH_INFO'] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/batch" + end + + describe 'download' do + describe 'when user is authenticated' do + before do + body = { 'operation' => 'download', + 'objects' => [ + { 'oid' => sample_oid, + 'size' => sample_size + }] + }.to_json + env['rack.input'] = StringIO.new(body) + end + + describe 'when user has download access' do + before do + @auth = authorize(user) + env["HTTP_AUTHORIZATION"] = @auth + project.team << [user, :reporter] + end + + context 'when downloading an lfs object that is assigned to our project' do + before do + project.lfs_objects << lfs_object + end + + it 'responds with status 200 and href to download' do + response = lfs_router_auth.try_call + expect(response.first).to eq(200) + response_body = ActiveSupport::JSON.decode(response.last.first) + + expect(response_body).to eq('objects' => [ + { 'oid' => sample_oid, + 'size' => sample_size, + 'actions' => { + 'download' => { + 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", + 'header' => { 'Authorization' => @auth } + } + } + }]) + end + end + + context 'when downloading an lfs object that is assigned to other project' do + before do + public_project.lfs_objects << lfs_object + end + + it 'responds with status 200 and error message' do + response = lfs_router_auth.try_call + expect(response.first).to eq(200) + response_body = ActiveSupport::JSON.decode(response.last.first) + + expect(response_body).to eq('objects' => [ + { 'oid' => sample_oid, + 'size' => sample_size, + 'error' => { + 'code' => 404, + 'message' => "Object does not exist on the server or you don't have permissions to access it", + } + }]) + end + end + + context 'when downloading a lfs object that does not exist' do + before do + body = { 'operation' => 'download', + 'objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078 + }] + }.to_json + env['rack.input'] = StringIO.new(body) + end + + it "responds with status 200 and error message" do + response = lfs_router_auth.try_call + expect(response.first).to eq(200) + response_body = ActiveSupport::JSON.decode(response.last.first) + + expect(response_body).to eq('objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078, + 'error' => { + 'code' => 404, + 'message' => "Object does not exist on the server or you don't have permissions to access it", + } + }]) + end + end + + context 'when downloading one new and one existing lfs object' do + before do + body = { 'operation' => 'download', + 'objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078 + }, + { 'oid' => sample_oid, + 'size' => sample_size + } + ] + }.to_json + env['rack.input'] = StringIO.new(body) + project.lfs_objects << lfs_object + end + + it "responds with status 200 with upload hypermedia link for the new object" do + response = lfs_router_auth.try_call + expect(response.first).to eq(200) + response_body = ActiveSupport::JSON.decode(response.last.first) + + expect(response_body).to eq('objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078, + 'error' => { + 'code' => 404, + 'message' => "Object does not exist on the server or you don't have permissions to access it", + } + }, + { 'oid' => sample_oid, + 'size' => sample_size, + 'actions' => { + 'download' => { + 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", + 'header' => { 'Authorization' => @auth } + } + } + }]) + end + end + end + + context 'when user does is not member of the project' do + before do + @auth = authorize(user) + env["HTTP_AUTHORIZATION"] = @auth + project.team << [user, :guest] + end + + it 'responds with 403' do + expect(lfs_router_auth.try_call.first).to eq(403) + end + end + + context 'when user does not have download access' do + before do + @auth = authorize(user) + env["HTTP_AUTHORIZATION"] = @auth + project.team << [user, :guest] + end + + it 'responds with 403' do + expect(lfs_router_auth.try_call.first).to eq(403) + end + end + end + + context 'when user is not authenticated' do + before do + body = { 'operation' => 'download', + 'objects' => [ + { 'oid' => sample_oid, + 'size' => sample_size + }], + + }.to_json + env['rack.input'] = StringIO.new(body) + end + + describe 'is accessing public project' do + before do + public_project.lfs_objects << lfs_object + end + + it 'responds with status 200 and href to download' do + response = lfs_router_public_noauth.try_call + expect(response.first).to eq(200) + response_body = ActiveSupport::JSON.decode(response.last.first) + + expect(response_body).to eq('objects' => [ + { 'oid' => sample_oid, + 'size' => sample_size, + 'actions' => { + 'download' => { + 'href' => "#{public_project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", + 'header' => {} + } + } + }]) + end + end + + describe 'is accessing non-public project' do + before do + project.lfs_objects << lfs_object + end + + it 'responds with authorization required' do + expect(lfs_router_noauth.try_call.first).to eq(401) + end + end + end + end + + describe 'upload' do + describe 'when user is authenticated' do + before do + body = { 'operation' => 'upload', + 'objects' => [ + { 'oid' => sample_oid, + 'size' => sample_size + }] + }.to_json + env['rack.input'] = StringIO.new(body) + end + + describe 'when user has project push access' do + before do + @auth = authorize(user) + env["HTTP_AUTHORIZATION"] = @auth + project.team << [user, :developer] + end + + context 'when pushing an lfs object that already exists' do + before do + public_project.lfs_objects << lfs_object + end + + it "responds with status 200 and links the object to the project" do + response_body = lfs_router_auth.try_call.last + response = ActiveSupport::JSON.decode(response_body.first) + + expect(response['objects']).to be_kind_of(Array) + expect(response['objects'].first['oid']).to eq(sample_oid) + expect(response['objects'].first['size']).to eq(sample_size) + expect(lfs_object.projects.pluck(:id)).to_not include(project.id) + expect(lfs_object.projects.pluck(:id)).to include(public_project.id) + expect(response['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}") + expect(response['objects'].first['actions']['upload']['header']).to eq('Authorization' => @auth) + end + end + + context 'when pushing a lfs object that does not exist' do + before do + body = { 'operation' => 'upload', + 'objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078 + }] + }.to_json + env['rack.input'] = StringIO.new(body) + end + + it "responds with status 200 and upload hypermedia link" do + response = lfs_router_auth.try_call + expect(response.first).to eq(200) + + response_body = ActiveSupport::JSON.decode(response.last.first) + expect(response_body['objects']).to be_kind_of(Array) + expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") + expect(response_body['objects'].first['size']).to eq(1575078) + expect(lfs_object.projects.pluck(:id)).not_to include(project.id) + expect(response_body['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078") + expect(response_body['objects'].first['actions']['upload']['header']).to eq('Authorization' => @auth) + end + end + + context 'when pushing one new and one existing lfs object' do + before do + body = { 'operation' => 'upload', + 'objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078 + }, + { 'oid' => sample_oid, + 'size' => sample_size + } + ] + }.to_json + env['rack.input'] = StringIO.new(body) + project.lfs_objects << lfs_object + end + + it "responds with status 200 with upload hypermedia link for the new object" do + response = lfs_router_auth.try_call + expect(response.first).to eq(200) + + response_body = ActiveSupport::JSON.decode(response.last.first) + expect(response_body['objects']).to be_kind_of(Array) + + expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") + expect(response_body['objects'].first['size']).to eq(1575078) + expect(response_body['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078") + expect(response_body['objects'].first['actions']['upload']['header']).to eq("Authorization" => @auth) + + expect(response_body['objects'].last['oid']).to eq(sample_oid) + expect(response_body['objects'].last['size']).to eq(sample_size) + expect(response_body['objects'].last).to_not have_key('actions') + end + end + end + + context 'when user does not have push access' do + it 'responds with 403' do + expect(lfs_router_auth.try_call.first).to eq(403) + end + end + end + + context 'when user is not authenticated' do + before do + env['rack.input'] = StringIO.new( + { 'objects' => [], 'operation' => 'upload' }.to_json + ) + end + + context 'when user has push access' do + before do + project.team << [user, :master] + end + + it "responds with status 401" do + expect(lfs_router_public_noauth.try_call.first).to eq(401) + end + end + + context 'when user does not have push access' do + it "responds with status 401" do + expect(lfs_router_public_noauth.try_call.first).to eq(401) + end + end + end + end + + describe 'unsupported' do + before do + body = { 'operation' => 'other', + 'objects' => [ + { 'oid' => sample_oid, + 'size' => sample_size + }] + }.to_json + env['rack.input'] = StringIO.new(body) + end + + it 'responds with status 404' do + expect(lfs_router_public_noauth.try_call.first).to eq(404) + end + end + end + + describe 'when pushing a lfs object' do + before do + enable_lfs + env['REQUEST_METHOD'] = 'PUT' + end + + describe 'to one project' do + describe 'when user has push access to the project' do + before do + project.team << [user, :master] + end + + describe 'when user is authenticated' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(project) + end + + it 'responds with status 200, location of lfs store and object details' do + json_response = ActiveSupport::JSON.decode(lfs_router_auth.try_call.last.first) + + expect(lfs_router_auth.try_call.first).to eq(200) + expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload") + expect(json_response['LfsOid']).to eq(sample_oid) + expect(json_response['LfsSize']).to eq(sample_size) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(project) + end + + it 'responds with status 200 and lfs object is linked to the project' do + expect(lfs_router_auth.try_call.first).to eq(200) + expect(lfs_object.projects.pluck(:id)).to include(project.id) + end + end + end + + describe 'when user is unauthenticated' do + let(:lfs_router_noauth) { new_lfs_router(project, nil) } + + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(project) + end + + it 'responds with status 401' do + expect(lfs_router_noauth.try_call.first).to eq(401) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(project) + end + + it 'responds with status 401' do + expect(lfs_router_noauth.try_call.first).to eq(401) + end + end + + context 'and request is sent with a malformed headers' do + before do + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}" + env["HTTP_X_GITLAB_LFS_TMP"] = "cat /etc/passwd" + end + + it 'does not recognize it as a valid lfs command' do + expect(lfs_router_noauth.try_call).to eq(nil) + end + end + end + end + + describe 'and user does not have push access' do + describe 'when user is authenticated' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(project) + end + + it 'responds with 403' do + expect(lfs_router_auth.try_call.first).to eq(403) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(project) + end + + it 'responds with 403' do + expect(lfs_router_auth.try_call.first).to eq(403) + end + end + end + + describe 'when user is unauthenticated' do + let(:lfs_router_noauth) { new_lfs_router(project, nil) } + + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(project) + end + + it 'responds with 401' do + expect(lfs_router_noauth.try_call.first).to eq(401) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(project) + end + + it 'responds with 401' do + expect(lfs_router_noauth.try_call.first).to eq(401) + end + end + end + end + end + + describe "to a forked project" do + let(:forked_project) { fork_project(public_project, user) } + + describe 'when user has push access to the project' do + before do + forked_project.team << [user_two, :master] + end + + describe 'when user is authenticated' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(forked_project) + end + + it 'responds with status 200, location of lfs store and object details' do + json_response = ActiveSupport::JSON.decode(lfs_router_forked_auth.try_call.last.first) + + expect(lfs_router_forked_auth.try_call.first).to eq(200) + expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload") + expect(json_response['LfsOid']).to eq(sample_oid) + expect(json_response['LfsSize']).to eq(sample_size) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(forked_project) + end + + it 'responds with status 200 and lfs object is linked to the source project' do + expect(lfs_router_forked_auth.try_call.first).to eq(200) + expect(lfs_object.projects.pluck(:id)).to include(public_project.id) + end + end + end + + describe 'when user is unauthenticated' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(forked_project) + end + + it 'responds with status 401' do + expect(lfs_router_forked_noauth.try_call.first).to eq(401) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(forked_project) + end + + it 'responds with status 401' do + expect(lfs_router_forked_noauth.try_call.first).to eq(401) + end + end + end + end + + describe 'and user does not have push access' do + describe 'when user is authenticated' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(forked_project) + end + + it 'responds with 403' do + expect(lfs_router_forked_auth.try_call.first).to eq(403) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(forked_project) + end + + it 'responds with 403' do + expect(lfs_router_forked_auth.try_call.first).to eq(403) + end + end + end + + describe 'when user is unauthenticated' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(forked_project) + end + + it 'responds with 401' do + expect(lfs_router_forked_noauth.try_call.first).to eq(401) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(forked_project) + end + + it 'responds with 401' do + expect(lfs_router_forked_noauth.try_call.first).to eq(401) + end + end + end + end + + describe 'and second project not related to fork or a source project' do + let(:second_project) { create(:project) } + let(:lfs_router_second_project) { new_lfs_router(second_project, user) } + + before do + public_project.lfs_objects << lfs_object + headers_for_upload_finalize(second_project) + end + + context 'when pushing the same lfs object to the second project' do + before do + second_project.team << [user, :master] + end + + it 'responds with 200 and links the lfs object to the project' do + expect(lfs_router_second_project.try_call.first).to eq(200) + expect(lfs_object.projects.pluck(:id)).to include(second_project.id, public_project.id) + end + end + end + end + end + + def enable_lfs + allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) + end + + def authorize(user) + ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password) + end + + def new_lfs_router(project, user) + Gitlab::Lfs::Router.new(project, user, request) + end + + def header_for_upload_authorize(project) + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize" + end + + def headers_for_upload_finalize(project) + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}" + env["HTTP_X_GITLAB_LFS_TMP"] = "#{sample_oid}6e561c9d4" + end + + def fork_project(project, user, object = nil) + allow(RepositoryForkWorker).to receive(:perform_async).and_return(true) + Projects::ForkService.new(project, user, {}).execute + end +end diff --git a/spec/lib/gitlab/markdown/autolink_filter_spec.rb b/spec/lib/gitlab/markdown/autolink_filter_spec.rb deleted file mode 100644 index 26332ba5217..00000000000 --- a/spec/lib/gitlab/markdown/autolink_filter_spec.rb +++ /dev/null @@ -1,114 +0,0 @@ -require 'spec_helper' - -module Gitlab::Markdown - describe AutolinkFilter do - include FilterSpecHelper - - let(:link) { 'http://about.gitlab.com/' } - - it 'does nothing when :autolink is false' do - exp = act = link - expect(filter(act, autolink: false).to_html).to eq exp - end - - it 'does nothing with non-link text' do - exp = act = 'This text contains no links to autolink' - expect(filter(act).to_html).to eq exp - end - - context 'Rinku schemes' do - it 'autolinks http' do - doc = filter("See #{link}") - expect(doc.at_css('a').text).to eq link - expect(doc.at_css('a')['href']).to eq link - end - - it 'autolinks https' do - link = 'https://google.com/' - doc = filter("See #{link}") - - expect(doc.at_css('a').text).to eq link - expect(doc.at_css('a')['href']).to eq link - end - - it 'autolinks ftp' do - link = 'ftp://ftp.us.debian.org/debian/' - doc = filter("See #{link}") - - expect(doc.at_css('a').text).to eq link - expect(doc.at_css('a')['href']).to eq link - end - - it 'autolinks short URLs' do - link = 'http://localhost:3000/' - doc = filter("See #{link}") - - expect(doc.at_css('a').text).to eq link - expect(doc.at_css('a')['href']).to eq link - end - - it 'accepts link_attr options' do - doc = filter("See #{link}", link_attr: { class: 'custom' }) - - expect(doc.at_css('a')['class']).to eq 'custom' - end - - described_class::IGNORE_PARENTS.each do |elem| - it "ignores valid links contained inside '#{elem}' element" do - exp = act = "<#{elem}>See #{link}</#{elem}>" - expect(filter(act).to_html).to eq exp - end - end - end - - context 'other schemes' do - let(:link) { 'foo://bar.baz/' } - - it 'autolinks smb' do - link = 'smb:///Volumes/shared/foo.pdf' - doc = filter("See #{link}") - - expect(doc.at_css('a').text).to eq link - expect(doc.at_css('a')['href']).to eq link - end - - it 'autolinks irc' do - link = 'irc://irc.freenode.net/git' - doc = filter("See #{link}") - - expect(doc.at_css('a').text).to eq link - expect(doc.at_css('a')['href']).to eq link - end - - it 'does not include trailing punctuation' do - doc = filter("See #{link}.") - expect(doc.at_css('a').text).to eq link - - doc = filter("See #{link}, ok?") - expect(doc.at_css('a').text).to eq link - - doc = filter("See #{link}...") - expect(doc.at_css('a').text).to eq link - end - - it 'does not include trailing HTML entities' do - doc = filter("See <<<#{link}>>>") - - expect(doc.at_css('a')['href']).to eq link - expect(doc.text).to eq "See <<<#{link}>>>" - end - - it 'accepts link_attr options' do - doc = filter("See #{link}", link_attr: { class: 'custom' }) - expect(doc.at_css('a')['class']).to eq 'custom' - end - - described_class::IGNORE_PARENTS.each do |elem| - it "ignores valid links contained inside '#{elem}' element" do - exp = act = "<#{elem}>See #{link}</#{elem}>" - expect(filter(act).to_html).to eq exp - end - end - end - end -end diff --git a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb deleted file mode 100644 index e5b8d723fe5..00000000000 --- a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb +++ /dev/null @@ -1,145 +0,0 @@ -require 'spec_helper' - -module Gitlab::Markdown - describe CommitRangeReferenceFilter do - include FilterSpecHelper - - let(:project) { create(:project, :public) } - let(:commit1) { project.commit } - let(:commit2) { project.commit("HEAD~2") } - - let(:range) { CommitRange.new("#{commit1.id}...#{commit2.id}") } - let(:range2) { CommitRange.new("#{commit1.id}..#{commit2.id}") } - - it 'requires project context' do - expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) - end - - %w(pre code a style).each do |elem| - it "ignores valid references contained inside '#{elem}' element" do - exp = act = "<#{elem}>Commit Range #{range.to_reference}</#{elem}>" - expect(filter(act).to_html).to eq exp - end - end - - context 'internal reference' do - let(:reference) { range.to_reference } - let(:reference2) { range2.to_reference } - - it 'links to a valid two-dot reference' do - doc = filter("See #{reference2}") - - expect(doc.css('a').first.attr('href')). - to eq urls.namespace_project_compare_url(project.namespace, project, range2.to_param) - end - - it 'links to a valid three-dot reference' do - doc = filter("See #{reference}") - - expect(doc.css('a').first.attr('href')). - to eq urls.namespace_project_compare_url(project.namespace, project, range.to_param) - end - - it 'links to a valid short ID' do - reference = "#{commit1.short_id}...#{commit2.id}" - reference2 = "#{commit1.id}...#{commit2.short_id}" - - exp = commit1.short_id + '...' + commit2.short_id - - expect(filter("See #{reference}").css('a').first.text).to eq exp - expect(filter("See #{reference2}").css('a').first.text).to eq exp - end - - it 'links with adjacent text' do - doc = filter("See (#{reference}.)") - - exp = Regexp.escape(range.to_s) - expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/) - end - - it 'ignores invalid commit IDs' do - exp = act = "See #{commit1.id.reverse}...#{commit2.id}" - - expect(project).to receive(:valid_repo?).and_return(true) - expect(project.repository).to receive(:commit).with(commit1.id.reverse) - expect(filter(act).to_html).to eq exp - end - - it 'includes a title attribute' do - doc = filter("See #{reference}") - expect(doc.css('a').first.attr('title')).to eq range.reference_title - end - - it 'includes default classes' do - doc = filter("See #{reference}") - expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range' - end - - it 'includes a data-project attribute' do - doc = filter("See #{reference}") - link = doc.css('a').first - - expect(link).to have_attribute('data-project') - expect(link.attr('data-project')).to eq project.id.to_s - end - - it 'includes a data-commit-range attribute' do - doc = filter("See #{reference}") - link = doc.css('a').first - - expect(link).to have_attribute('data-commit-range') - expect(link.attr('data-commit-range')).to eq range.to_reference - end - - it 'supports an :only_path option' do - doc = filter("See #{reference}", only_path: true) - link = doc.css('a').first.attr('href') - - expect(link).not_to match %r(https?://) - expect(link).to eq urls.namespace_project_compare_url(project.namespace, project, from: commit1.id, to: commit2.id, only_path: true) - end - - it 'adds to the results hash' do - result = reference_pipeline_result("See #{reference}") - expect(result[:references][:commit_range]).not_to be_empty - end - end - - context 'cross-project reference' do - let(:namespace) { create(:namespace, name: 'cross-reference') } - let(:project2) { create(:project, :public, namespace: namespace) } - let(:reference) { range.to_reference(project) } - - before do - range.project = project2 - end - - it 'links to a valid reference' do - doc = filter("See #{reference}") - - expect(doc.css('a').first.attr('href')). - to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param) - end - - it 'links with adjacent text' do - doc = filter("Fixed (#{reference}.)") - - exp = Regexp.escape("#{project2.to_reference}@#{range.to_s}") - expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/) - end - - it 'ignores invalid commit IDs on the referenced project' do - exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}" - expect(filter(act).to_html).to eq exp - - exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}" - expect(filter(act).to_html).to eq exp - end - - it 'adds to the results hash' do - result = reference_pipeline_result("See #{reference}") - expect(result[:references][:commit_range]).not_to be_empty - end - end - end -end diff --git a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb deleted file mode 100644 index d080efbf3d4..00000000000 --- a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb +++ /dev/null @@ -1,135 +0,0 @@ -require 'spec_helper' - -module Gitlab::Markdown - describe CommitReferenceFilter do - include FilterSpecHelper - - let(:project) { create(:project, :public) } - let(:commit) { project.commit } - - it 'requires project context' do - expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) - end - - %w(pre code a style).each do |elem| - it "ignores valid references contained inside '#{elem}' element" do - exp = act = "<#{elem}>Commit #{commit.id}</#{elem}>" - expect(filter(act).to_html).to eq exp - end - end - - context 'internal reference' do - let(:reference) { commit.id } - - # Let's test a variety of commit SHA sizes just to be paranoid - [6, 8, 12, 18, 20, 32, 40].each do |size| - it "links to a valid reference of #{size} characters" do - doc = filter("See #{reference[0...size]}") - - expect(doc.css('a').first.text).to eq commit.short_id - expect(doc.css('a').first.attr('href')). - to eq urls.namespace_project_commit_url(project.namespace, project, reference) - end - end - - it 'always uses the short ID as the link text' do - doc = filter("See #{commit.id}") - expect(doc.text).to eq "See #{commit.short_id}" - - doc = filter("See #{commit.id[0...6]}") - expect(doc.text).to eq "See #{commit.short_id}" - end - - it 'links with adjacent text' do - doc = filter("See (#{reference}.)") - expect(doc.to_html).to match(/\(<a.+>#{commit.short_id}<\/a>\.\)/) - end - - it 'ignores invalid commit IDs' do - invalid = invalidate_reference(reference) - exp = act = "See #{invalid}" - - expect(project).to receive(:valid_repo?).and_return(true) - expect(project.repository).to receive(:commit).with(invalid) - expect(filter(act).to_html).to eq exp - end - - it 'includes a title attribute' do - doc = filter("See #{reference}") - expect(doc.css('a').first.attr('title')).to eq commit.link_title - end - - it 'escapes the title attribute' do - allow_any_instance_of(Commit).to receive(:title).and_return(%{"></a>whatever<a title="}) - - doc = filter("See #{reference}") - expect(doc.text).to eq "See #{commit.short_id}" - end - - it 'includes default classes' do - doc = filter("See #{reference}") - expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit' - end - - it 'includes a data-project attribute' do - doc = filter("See #{reference}") - link = doc.css('a').first - - expect(link).to have_attribute('data-project') - expect(link.attr('data-project')).to eq project.id.to_s - end - - it 'includes a data-commit attribute' do - doc = filter("See #{reference}") - link = doc.css('a').first - - expect(link).to have_attribute('data-commit') - expect(link.attr('data-commit')).to eq commit.id - end - - it 'supports an :only_path context' do - doc = filter("See #{reference}", only_path: true) - link = doc.css('a').first.attr('href') - - expect(link).not_to match %r(https?://) - expect(link).to eq urls.namespace_project_commit_url(project.namespace, project, reference, only_path: true) - end - - it 'adds to the results hash' do - result = reference_pipeline_result("See #{reference}") - expect(result[:references][:commit]).not_to be_empty - end - end - - context 'cross-project reference' do - let(:namespace) { create(:namespace, name: 'cross-reference') } - let(:project2) { create(:project, :public, namespace: namespace) } - let(:commit) { project2.commit } - let(:reference) { commit.to_reference(project) } - - it 'links to a valid reference' do - doc = filter("See #{reference}") - - expect(doc.css('a').first.attr('href')). - to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id) - end - - it 'links with adjacent text' do - doc = filter("Fixed (#{reference}.)") - - exp = Regexp.escape(project2.to_reference) - expect(doc.to_html).to match(/\(<a.+>#{exp}@#{commit.short_id}<\/a>\.\)/) - end - - it 'ignores invalid commit IDs on the referenced project' do - exp = act = "Committed #{invalidate_reference(reference)}" - expect(filter(act).to_html).to eq exp - end - - it 'adds to the results hash' do - result = reference_pipeline_result("See #{reference}") - expect(result[:references][:commit]).not_to be_empty - end - end - end -end diff --git a/spec/lib/gitlab/markdown/cross_project_reference_spec.rb b/spec/lib/gitlab/markdown/cross_project_reference_spec.rb index 8d4f9e403a6..f594fe4ccf6 100644 --- a/spec/lib/gitlab/markdown/cross_project_reference_spec.rb +++ b/spec/lib/gitlab/markdown/cross_project_reference_spec.rb @@ -1,35 +1,33 @@ require 'spec_helper' -module Gitlab::Markdown - describe CrossProjectReference do - include described_class +describe Gitlab::Markdown::CrossProjectReference, lib: true do + include described_class - describe '#project_from_ref' do - context 'when no project was referenced' do - it 'returns the project from context' do - project = double + describe '#project_from_ref' do + context 'when no project was referenced' do + it 'returns the project from context' do + project = double - allow(self).to receive(:context).and_return({ project: project }) + allow(self).to receive(:context).and_return({ project: project }) - expect(project_from_ref(nil)).to eq project - end + expect(project_from_ref(nil)).to eq project end + end - context 'when referenced project does not exist' do - it 'returns nil' do - expect(project_from_ref('invalid/reference')).to be_nil - end + context 'when referenced project does not exist' do + it 'returns nil' do + expect(project_from_ref('invalid/reference')).to be_nil end + end - context 'when referenced project exists' do - it 'returns the referenced project' do - project2 = double('referenced project') + context 'when referenced project exists' do + it 'returns the referenced project' do + project2 = double('referenced project') - expect(Project).to receive(:find_with_namespace). - with('cross/reference').and_return(project2) + expect(Project).to receive(:find_with_namespace). + with('cross/reference').and_return(project2) - expect(project_from_ref('cross/reference')).to eq project2 - end + expect(project_from_ref('cross/reference')).to eq project2 end end end diff --git a/spec/lib/gitlab/markdown/emoji_filter_spec.rb b/spec/lib/gitlab/markdown/emoji_filter_spec.rb deleted file mode 100644 index 11efd9bb4cd..00000000000 --- a/spec/lib/gitlab/markdown/emoji_filter_spec.rb +++ /dev/null @@ -1,95 +0,0 @@ -require 'spec_helper' - -module Gitlab::Markdown - describe EmojiFilter do - include FilterSpecHelper - - before do - ActionController::Base.asset_host = 'https://foo.com' - end - - it 'replaces supported emoji' do - doc = filter('<p>:heart:</p>') - expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/emoji/2764.png' - end - - it 'ignores unsupported emoji' do - exp = act = '<p>:foo:</p>' - doc = filter(act) - expect(doc.to_html).to match Regexp.escape(exp) - end - - it 'correctly encodes the URL' do - doc = filter('<p>:+1:</p>') - expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/emoji/1F44D.png' - end - - it 'matches at the start of a string' do - doc = filter(':+1:') - expect(doc.css('img').size).to eq 1 - end - - it 'matches at the end of a string' do - doc = filter('This gets a :-1:') - expect(doc.css('img').size).to eq 1 - end - - it 'matches with adjacent text' do - doc = filter('+1 (:+1:)') - expect(doc.css('img').size).to eq 1 - end - - it 'matches multiple emoji in a row' do - doc = filter(':see_no_evil::hear_no_evil::speak_no_evil:') - expect(doc.css('img').size).to eq 3 - end - - it 'has a title attribute' do - doc = filter(':-1:') - expect(doc.css('img').first.attr('title')).to eq ':-1:' - end - - it 'has an alt attribute' do - doc = filter(':-1:') - expect(doc.css('img').first.attr('alt')).to eq ':-1:' - end - - it 'has an align attribute' do - doc = filter(':8ball:') - expect(doc.css('img').first.attr('align')).to eq 'absmiddle' - end - - it 'has an emoji class' do - doc = filter(':cat:') - expect(doc.css('img').first.attr('class')).to eq 'emoji' - end - - it 'has height and width attributes' do - doc = filter(':dog:') - img = doc.css('img').first - - expect(img.attr('width')).to eq '20' - expect(img.attr('height')).to eq '20' - end - - it 'keeps whitespace intact' do - doc = filter('This deserves a :+1:, big time.') - - expect(doc.to_html).to match(/^This deserves a <img.+>, big time\.\z/) - end - - it 'uses a custom asset_root context' do - root = Gitlab.config.gitlab.url + 'gitlab/root' - - doc = filter(':smile:', asset_root: root) - expect(doc.css('img').first.attr('src')).to start_with(root) - end - - it 'uses a custom asset_host context' do - ActionController::Base.asset_host = 'https://cdn.example.com' - - doc = filter(':frowning:', asset_host: 'https://this-is-ignored-i-guess?') - expect(doc.css('img').first.attr('src')).to start_with('https://cdn.example.com') - end - end -end diff --git a/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb deleted file mode 100644 index d8c2970b6bd..00000000000 --- a/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb +++ /dev/null @@ -1,79 +0,0 @@ -require 'spec_helper' - -module Gitlab::Markdown - describe ExternalIssueReferenceFilter do - include FilterSpecHelper - - def helper - IssuesHelper - end - - let(:project) { create(:jira_project) } - - context 'JIRA issue references' do - let(:issue) { ExternalIssue.new('JIRA-123', project) } - let(:reference) { issue.to_reference } - - it 'requires project context' do - expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) - end - - %w(pre code a style).each do |elem| - it "ignores valid references contained inside '#{elem}' element" do - exp = act = "<#{elem}>Issue #{reference}</#{elem}>" - expect(filter(act).to_html).to eq exp - end - end - - it 'ignores valid references when using default tracker' do - expect(project).to receive(:default_issues_tracker?).and_return(true) - - exp = act = "Issue #{reference}" - expect(filter(act).to_html).to eq exp - end - - it 'links to a valid reference' do - doc = filter("Issue #{reference}") - expect(doc.css('a').first.attr('href')) - .to eq helper.url_for_issue(reference, project) - end - - it 'links to the external tracker' do - doc = filter("Issue #{reference}") - link = doc.css('a').first.attr('href') - - expect(link).to eq "http://jira.example/browse/#{reference}" - end - - it 'links with adjacent text' do - doc = filter("Issue (#{reference}.)") - expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/) - end - - it 'includes a title attribute' do - doc = filter("Issue #{reference}") - expect(doc.css('a').first.attr('title')).to eq "Issue in JIRA tracker" - end - - it 'escapes the title attribute' do - allow(project.external_issue_tracker).to receive(:title). - and_return(%{"></a>whatever<a title="}) - - doc = filter("Issue #{reference}") - expect(doc.text).to eq "Issue #{reference}" - end - - it 'includes default classes' do - doc = filter("Issue #{reference}") - expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue' - end - - it 'supports an :only_path context' do - doc = filter("Issue #{reference}", only_path: true) - link = doc.css('a').first.attr('href') - - expect(link).to eq helper.url_for_issue("#{reference}", project, only_path: true) - end - end - end -end diff --git a/spec/lib/gitlab/markdown/external_link_filter_spec.rb b/spec/lib/gitlab/markdown/external_link_filter_spec.rb deleted file mode 100644 index a040b34577b..00000000000 --- a/spec/lib/gitlab/markdown/external_link_filter_spec.rb +++ /dev/null @@ -1,31 +0,0 @@ -require 'spec_helper' - -module Gitlab::Markdown - describe ExternalLinkFilter do - include FilterSpecHelper - - it 'ignores elements without an href attribute' do - exp = act = %q(<a id="ignored">Ignore Me</a>) - expect(filter(act).to_html).to eq exp - end - - it 'ignores non-HTTP(S) links' do - exp = act = %q(<a href="irc://irc.freenode.net/gitlab">IRC</a>) - expect(filter(act).to_html).to eq exp - end - - it 'skips internal links' do - internal = Gitlab.config.gitlab.url - exp = act = %Q(<a href="#{internal}/sign_in">Login</a>) - expect(filter(act).to_html).to eq exp - end - - it 'adds rel="nofollow" to external links' do - act = %q(<a href="https://google.com/">Google</a>) - doc = filter(act) - - expect(doc.at_css('a')).to have_attribute('rel') - expect(doc.at_css('a')['rel']).to eq 'nofollow' - end - end -end diff --git a/spec/lib/gitlab/markdown/filter/autolink_filter_spec.rb b/spec/lib/gitlab/markdown/filter/autolink_filter_spec.rb new file mode 100644 index 00000000000..a0844aee559 --- /dev/null +++ b/spec/lib/gitlab/markdown/filter/autolink_filter_spec.rb @@ -0,0 +1,112 @@ +require 'spec_helper' + +describe Gitlab::Markdown::AutolinkFilter, lib: true do + include FilterSpecHelper + + let(:link) { 'http://about.gitlab.com/' } + + it 'does nothing when :autolink is false' do + exp = act = link + expect(filter(act, autolink: false).to_html).to eq exp + end + + it 'does nothing with non-link text' do + exp = act = 'This text contains no links to autolink' + expect(filter(act).to_html).to eq exp + end + + context 'Rinku schemes' do + it 'autolinks http' do + doc = filter("See #{link}") + expect(doc.at_css('a').text).to eq link + expect(doc.at_css('a')['href']).to eq link + end + + it 'autolinks https' do + link = 'https://google.com/' + doc = filter("See #{link}") + + expect(doc.at_css('a').text).to eq link + expect(doc.at_css('a')['href']).to eq link + end + + it 'autolinks ftp' do + link = 'ftp://ftp.us.debian.org/debian/' + doc = filter("See #{link}") + + expect(doc.at_css('a').text).to eq link + expect(doc.at_css('a')['href']).to eq link + end + + it 'autolinks short URLs' do + link = 'http://localhost:3000/' + doc = filter("See #{link}") + + expect(doc.at_css('a').text).to eq link + expect(doc.at_css('a')['href']).to eq link + end + + it 'accepts link_attr options' do + doc = filter("See #{link}", link_attr: { class: 'custom' }) + + expect(doc.at_css('a')['class']).to eq 'custom' + end + + described_class::IGNORE_PARENTS.each do |elem| + it "ignores valid links contained inside '#{elem}' element" do + exp = act = "<#{elem}>See #{link}</#{elem}>" + expect(filter(act).to_html).to eq exp + end + end + end + + context 'other schemes' do + let(:link) { 'foo://bar.baz/' } + + it 'autolinks smb' do + link = 'smb:///Volumes/shared/foo.pdf' + doc = filter("See #{link}") + + expect(doc.at_css('a').text).to eq link + expect(doc.at_css('a')['href']).to eq link + end + + it 'autolinks irc' do + link = 'irc://irc.freenode.net/git' + doc = filter("See #{link}") + + expect(doc.at_css('a').text).to eq link + expect(doc.at_css('a')['href']).to eq link + end + + it 'does not include trailing punctuation' do + doc = filter("See #{link}.") + expect(doc.at_css('a').text).to eq link + + doc = filter("See #{link}, ok?") + expect(doc.at_css('a').text).to eq link + + doc = filter("See #{link}...") + expect(doc.at_css('a').text).to eq link + end + + it 'does not include trailing HTML entities' do + doc = filter("See <<<#{link}>>>") + + expect(doc.at_css('a')['href']).to eq link + expect(doc.text).to eq "See <<<#{link}>>>" + end + + it 'accepts link_attr options' do + doc = filter("See #{link}", link_attr: { class: 'custom' }) + expect(doc.at_css('a')['class']).to eq 'custom' + end + + described_class::IGNORE_PARENTS.each do |elem| + it "ignores valid links contained inside '#{elem}' element" do + exp = act = "<#{elem}>See #{link}</#{elem}>" + expect(filter(act).to_html).to eq exp + end + end + end +end diff --git a/spec/lib/gitlab/markdown/filter/commit_range_reference_filter_spec.rb b/spec/lib/gitlab/markdown/filter/commit_range_reference_filter_spec.rb new file mode 100644 index 00000000000..570c9767628 --- /dev/null +++ b/spec/lib/gitlab/markdown/filter/commit_range_reference_filter_spec.rb @@ -0,0 +1,182 @@ +require 'spec_helper' + +describe Gitlab::Markdown::CommitRangeReferenceFilter, lib: true do + include FilterSpecHelper + + let(:project) { create(:project, :public) } + let(:commit1) { project.commit("HEAD~2") } + let(:commit2) { project.commit } + + let(:range) { CommitRange.new("#{commit1.id}...#{commit2.id}", project) } + let(:range2) { CommitRange.new("#{commit1.id}..#{commit2.id}", project) } + + it 'requires project context' do + expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) + end + + %w(pre code a style).each do |elem| + it "ignores valid references contained inside '#{elem}' element" do + exp = act = "<#{elem}>Commit Range #{range.to_reference}</#{elem}>" + expect(reference_filter(act).to_html).to eq exp + end + end + + context 'internal reference' do + let(:reference) { range.to_reference } + let(:reference2) { range2.to_reference } + + it 'links to a valid two-dot reference' do + doc = reference_filter("See #{reference2}") + + expect(doc.css('a').first.attr('href')). + to eq urls.namespace_project_compare_url(project.namespace, project, range2.to_param) + end + + it 'links to a valid three-dot reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq urls.namespace_project_compare_url(project.namespace, project, range.to_param) + end + + it 'links to a valid short ID' do + reference = "#{commit1.short_id}...#{commit2.id}" + reference2 = "#{commit1.id}...#{commit2.short_id}" + + exp = commit1.short_id + '...' + commit2.short_id + + expect(reference_filter("See #{reference}").css('a').first.text).to eq exp + expect(reference_filter("See #{reference2}").css('a').first.text).to eq exp + end + + it 'links with adjacent text' do + doc = reference_filter("See (#{reference}.)") + + exp = Regexp.escape(range.reference_link_text) + expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/) + end + + it 'ignores invalid commit IDs' do + exp = act = "See #{commit1.id.reverse}...#{commit2.id}" + + expect(project).to receive(:valid_repo?).and_return(true) + expect(project.repository).to receive(:commit).with(commit1.id.reverse) + expect(project.repository).to receive(:commit).with(commit2.id) + expect(reference_filter(act).to_html).to eq exp + end + + it 'includes a title attribute' do + doc = reference_filter("See #{reference}") + expect(doc.css('a').first.attr('title')).to eq range.reference_title + end + + it 'includes default classes' do + doc = reference_filter("See #{reference}") + expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range' + end + + it 'includes a data-project attribute' do + doc = reference_filter("See #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-project') + expect(link.attr('data-project')).to eq project.id.to_s + end + + it 'includes a data-commit-range attribute' do + doc = reference_filter("See #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-commit-range') + expect(link.attr('data-commit-range')).to eq range.to_s + end + + it 'supports an :only_path option' do + doc = reference_filter("See #{reference}", only_path: true) + link = doc.css('a').first.attr('href') + + expect(link).not_to match %r(https?://) + expect(link).to eq urls.namespace_project_compare_url(project.namespace, project, from: commit1.id, to: commit2.id, only_path: true) + end + + it 'adds to the results hash' do + result = reference_pipeline_result("See #{reference}") + expect(result[:references][:commit_range]).not_to be_empty + end + end + + context 'cross-project reference' do + let(:namespace) { create(:namespace, name: 'cross-reference') } + let(:project2) { create(:project, :public, namespace: namespace) } + let(:reference) { range.to_reference(project) } + + before do + range.project = project2 + end + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param) + end + + it 'links with adjacent text' do + doc = reference_filter("Fixed (#{reference}.)") + + exp = Regexp.escape("#{project2.to_reference}@#{range.reference_link_text}") + expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/) + end + + it 'ignores invalid commit IDs on the referenced project' do + exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}" + expect(reference_filter(act).to_html).to eq exp + + exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}" + expect(reference_filter(act).to_html).to eq exp + end + + it 'adds to the results hash' do + result = reference_pipeline_result("See #{reference}") + expect(result[:references][:commit_range]).not_to be_empty + end + end + + context 'cross-project URL reference' do + let(:namespace) { create(:namespace, name: 'cross-reference') } + let(:project2) { create(:project, :public, namespace: namespace) } + let(:range) { CommitRange.new("#{commit1.id}...master", project) } + let(:reference) { urls.namespace_project_compare_url(project2.namespace, project2, from: commit1.id, to: 'master') } + + before do + range.project = project2 + end + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq reference + end + + it 'links with adjacent text' do + doc = reference_filter("Fixed (#{reference}.)") + + exp = Regexp.escape(range.reference_link_text(project)) + expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/) + end + + it 'ignores invalid commit IDs on the referenced project' do + exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}" + expect(reference_filter(act).to_html).to eq exp + + exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}" + expect(reference_filter(act).to_html).to eq exp + end + + it 'adds to the results hash' do + result = reference_pipeline_result("See #{reference}") + expect(result[:references][:commit_range]).not_to be_empty + end + end +end diff --git a/spec/lib/gitlab/markdown/filter/commit_reference_filter_spec.rb b/spec/lib/gitlab/markdown/filter/commit_reference_filter_spec.rb new file mode 100644 index 00000000000..76e7957bbb9 --- /dev/null +++ b/spec/lib/gitlab/markdown/filter/commit_reference_filter_spec.rb @@ -0,0 +1,163 @@ +require 'spec_helper' + +describe Gitlab::Markdown::CommitReferenceFilter, lib: true do + include FilterSpecHelper + + let(:project) { create(:project, :public) } + let(:commit) { project.commit } + + it 'requires project context' do + expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) + end + + %w(pre code a style).each do |elem| + it "ignores valid references contained inside '#{elem}' element" do + exp = act = "<#{elem}>Commit #{commit.id}</#{elem}>" + expect(reference_filter(act).to_html).to eq exp + end + end + + context 'internal reference' do + let(:reference) { commit.id } + + # Let's test a variety of commit SHA sizes just to be paranoid + [6, 8, 12, 18, 20, 32, 40].each do |size| + it "links to a valid reference of #{size} characters" do + doc = reference_filter("See #{reference[0...size]}") + + expect(doc.css('a').first.text).to eq commit.short_id + expect(doc.css('a').first.attr('href')). + to eq urls.namespace_project_commit_url(project.namespace, project, reference) + end + end + + it 'always uses the short ID as the link text' do + doc = reference_filter("See #{commit.id}") + expect(doc.text).to eq "See #{commit.short_id}" + + doc = reference_filter("See #{commit.id[0...6]}") + expect(doc.text).to eq "See #{commit.short_id}" + end + + it 'links with adjacent text' do + doc = reference_filter("See (#{reference}.)") + expect(doc.to_html).to match(/\(<a.+>#{commit.short_id}<\/a>\.\)/) + end + + it 'ignores invalid commit IDs' do + invalid = invalidate_reference(reference) + exp = act = "See #{invalid}" + + expect(project).to receive(:valid_repo?).and_return(true) + expect(project.repository).to receive(:commit).with(invalid) + expect(reference_filter(act).to_html).to eq exp + end + + it 'includes a title attribute' do + doc = reference_filter("See #{reference}") + expect(doc.css('a').first.attr('title')).to eq commit.link_title + end + + it 'escapes the title attribute' do + allow_any_instance_of(Commit).to receive(:title).and_return(%{"></a>whatever<a title="}) + + doc = reference_filter("See #{reference}") + expect(doc.text).to eq "See #{commit.short_id}" + end + + it 'includes default classes' do + doc = reference_filter("See #{reference}") + expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit' + end + + it 'includes a data-project attribute' do + doc = reference_filter("See #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-project') + expect(link.attr('data-project')).to eq project.id.to_s + end + + it 'includes a data-commit attribute' do + doc = reference_filter("See #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-commit') + expect(link.attr('data-commit')).to eq commit.id + end + + it 'supports an :only_path context' do + doc = reference_filter("See #{reference}", only_path: true) + link = doc.css('a').first.attr('href') + + expect(link).not_to match %r(https?://) + expect(link).to eq urls.namespace_project_commit_url(project.namespace, project, reference, only_path: true) + end + + it 'adds to the results hash' do + result = reference_pipeline_result("See #{reference}") + expect(result[:references][:commit]).not_to be_empty + end + end + + context 'cross-project reference' do + let(:namespace) { create(:namespace, name: 'cross-reference') } + let(:project2) { create(:project, :public, namespace: namespace) } + let(:commit) { project2.commit } + let(:reference) { commit.to_reference(project) } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id) + end + + it 'links with adjacent text' do + doc = reference_filter("Fixed (#{reference}.)") + + exp = Regexp.escape(project2.to_reference) + expect(doc.to_html).to match(/\(<a.+>#{exp}@#{commit.short_id}<\/a>\.\)/) + end + + it 'ignores invalid commit IDs on the referenced project' do + exp = act = "Committed #{invalidate_reference(reference)}" + expect(reference_filter(act).to_html).to eq exp + end + + it 'adds to the results hash' do + result = reference_pipeline_result("See #{reference}") + expect(result[:references][:commit]).not_to be_empty + end + end + + context 'cross-project URL reference' do + let(:namespace) { create(:namespace, name: 'cross-reference') } + let(:project2) { create(:project, :public, namespace: namespace) } + let(:commit) { project2.commit } + let(:reference) { urls.namespace_project_commit_url(project2.namespace, project2, commit.id) } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id) + end + + it 'links with adjacent text' do + doc = reference_filter("Fixed (#{reference}.)") + + expect(doc.to_html).to match(/\(<a.+>#{commit.reference_link_text(project)}<\/a>\.\)/) + end + + it 'ignores invalid commit IDs on the referenced project' do + act = "Committed #{invalidate_reference(reference)}" + expect(reference_filter(act).to_html).to match(/<a.+>#{Regexp.escape(invalidate_reference(reference))}<\/a>/) + end + + it 'adds to the results hash' do + result = reference_pipeline_result("See #{reference}") + expect(result[:references][:commit]).not_to be_empty + end + end +end diff --git a/spec/lib/gitlab/markdown/filter/emoji_filter_spec.rb b/spec/lib/gitlab/markdown/filter/emoji_filter_spec.rb new file mode 100644 index 00000000000..ea9b81862cf --- /dev/null +++ b/spec/lib/gitlab/markdown/filter/emoji_filter_spec.rb @@ -0,0 +1,98 @@ +require 'spec_helper' + +describe Gitlab::Markdown::EmojiFilter, lib: true do + include FilterSpecHelper + + before do + @original_asset_host = ActionController::Base.asset_host + ActionController::Base.asset_host = 'https://foo.com' + end + + after do + ActionController::Base.asset_host = @original_asset_host + end + + it 'replaces supported emoji' do + doc = filter('<p>:heart:</p>') + expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/emoji/2764.png' + end + + it 'ignores unsupported emoji' do + exp = act = '<p>:foo:</p>' + doc = filter(act) + expect(doc.to_html).to match Regexp.escape(exp) + end + + it 'correctly encodes the URL' do + doc = filter('<p>:+1:</p>') + expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/emoji/1F44D.png' + end + + it 'matches at the start of a string' do + doc = filter(':+1:') + expect(doc.css('img').size).to eq 1 + end + + it 'matches at the end of a string' do + doc = filter('This gets a :-1:') + expect(doc.css('img').size).to eq 1 + end + + it 'matches with adjacent text' do + doc = filter('+1 (:+1:)') + expect(doc.css('img').size).to eq 1 + end + + it 'matches multiple emoji in a row' do + doc = filter(':see_no_evil::hear_no_evil::speak_no_evil:') + expect(doc.css('img').size).to eq 3 + end + + it 'has a title attribute' do + doc = filter(':-1:') + expect(doc.css('img').first.attr('title')).to eq ':-1:' + end + + it 'has an alt attribute' do + doc = filter(':-1:') + expect(doc.css('img').first.attr('alt')).to eq ':-1:' + end + + it 'has an align attribute' do + doc = filter(':8ball:') + expect(doc.css('img').first.attr('align')).to eq 'absmiddle' + end + + it 'has an emoji class' do + doc = filter(':cat:') + expect(doc.css('img').first.attr('class')).to eq 'emoji' + end + + it 'has height and width attributes' do + doc = filter(':dog:') + img = doc.css('img').first + + expect(img.attr('width')).to eq '20' + expect(img.attr('height')).to eq '20' + end + + it 'keeps whitespace intact' do + doc = filter('This deserves a :+1:, big time.') + + expect(doc.to_html).to match(/^This deserves a <img.+>, big time\.\z/) + end + + it 'uses a custom asset_root context' do + root = Gitlab.config.gitlab.url + 'gitlab/root' + + doc = filter(':smile:', asset_root: root) + expect(doc.css('img').first.attr('src')).to start_with(root) + end + + it 'uses a custom asset_host context' do + ActionController::Base.asset_host = 'https://cdn.example.com' + + doc = filter(':frowning:', asset_host: 'https://this-is-ignored-i-guess?') + expect(doc.css('img').first.attr('src')).to start_with('https://cdn.example.com') + end +end diff --git a/spec/lib/gitlab/markdown/filter/external_issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/filter/external_issue_reference_filter_spec.rb new file mode 100644 index 00000000000..d8420102648 --- /dev/null +++ b/spec/lib/gitlab/markdown/filter/external_issue_reference_filter_spec.rb @@ -0,0 +1,77 @@ +require 'spec_helper' + +describe Gitlab::Markdown::ExternalIssueReferenceFilter, lib: true do + include FilterSpecHelper + + def helper + IssuesHelper + end + + let(:project) { create(:jira_project) } + + context 'JIRA issue references' do + let(:issue) { ExternalIssue.new('JIRA-123', project) } + let(:reference) { issue.to_reference } + + it 'requires project context' do + expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) + end + + %w(pre code a style).each do |elem| + it "ignores valid references contained inside '#{elem}' element" do + exp = act = "<#{elem}>Issue #{reference}</#{elem}>" + expect(filter(act).to_html).to eq exp + end + end + + it 'ignores valid references when using default tracker' do + expect(project).to receive(:default_issues_tracker?).and_return(true) + + exp = act = "Issue #{reference}" + expect(filter(act).to_html).to eq exp + end + + it 'links to a valid reference' do + doc = filter("Issue #{reference}") + expect(doc.css('a').first.attr('href')) + .to eq helper.url_for_issue(reference, project) + end + + it 'links to the external tracker' do + doc = filter("Issue #{reference}") + link = doc.css('a').first.attr('href') + + expect(link).to eq "http://jira.example/browse/#{reference}" + end + + it 'links with adjacent text' do + doc = filter("Issue (#{reference}.)") + expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/) + end + + it 'includes a title attribute' do + doc = filter("Issue #{reference}") + expect(doc.css('a').first.attr('title')).to eq "Issue in JIRA tracker" + end + + it 'escapes the title attribute' do + allow(project.external_issue_tracker).to receive(:title). + and_return(%{"></a>whatever<a title="}) + + doc = filter("Issue #{reference}") + expect(doc.text).to eq "Issue #{reference}" + end + + it 'includes default classes' do + doc = filter("Issue #{reference}") + expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue' + end + + it 'supports an :only_path context' do + doc = filter("Issue #{reference}", only_path: true) + link = doc.css('a').first.attr('href') + + expect(link).to eq helper.url_for_issue("#{reference}", project, only_path: true) + end + end +end diff --git a/spec/lib/gitlab/markdown/filter/external_link_filter_spec.rb b/spec/lib/gitlab/markdown/filter/external_link_filter_spec.rb new file mode 100644 index 00000000000..e559f5741cc --- /dev/null +++ b/spec/lib/gitlab/markdown/filter/external_link_filter_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe Gitlab::Markdown::ExternalLinkFilter, lib: true do + include FilterSpecHelper + + it 'ignores elements without an href attribute' do + exp = act = %q(<a id="ignored">Ignore Me</a>) + expect(filter(act).to_html).to eq exp + end + + it 'ignores non-HTTP(S) links' do + exp = act = %q(<a href="irc://irc.freenode.net/gitlab">IRC</a>) + expect(filter(act).to_html).to eq exp + end + + it 'skips internal links' do + internal = Gitlab.config.gitlab.url + exp = act = %Q(<a href="#{internal}/sign_in">Login</a>) + expect(filter(act).to_html).to eq exp + end + + it 'adds rel="nofollow" to external links' do + act = %q(<a href="https://google.com/">Google</a>) + doc = filter(act) + + expect(doc.at_css('a')).to have_attribute('rel') + expect(doc.at_css('a')['rel']).to eq 'nofollow' + end +end diff --git a/spec/lib/gitlab/markdown/filter/issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/filter/issue_reference_filter_spec.rb new file mode 100644 index 00000000000..1aa5d44568e --- /dev/null +++ b/spec/lib/gitlab/markdown/filter/issue_reference_filter_spec.rb @@ -0,0 +1,209 @@ +require 'spec_helper' + +describe Gitlab::Markdown::IssueReferenceFilter, lib: true do + include FilterSpecHelper + + def helper + IssuesHelper + end + + let(:project) { create(:empty_project, :public) } + let(:issue) { create(:issue, project: project) } + + it 'requires project context' do + expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) + end + + %w(pre code a style).each do |elem| + it "ignores valid references contained inside '#{elem}' element" do + exp = act = "<#{elem}>Issue #{issue.to_reference}</#{elem}>" + expect(reference_filter(act).to_html).to eq exp + end + end + + context 'internal reference' do + let(:reference) { issue.to_reference } + + it 'ignores valid references when using non-default tracker' do + expect(project).to receive(:get_issue).with(issue.iid).and_return(nil) + + exp = act = "Issue #{reference}" + expect(reference_filter(act).to_html).to eq exp + end + + it 'links to a valid reference' do + doc = reference_filter("Fixed #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq helper.url_for_issue(issue.iid, project) + end + + it 'links with adjacent text' do + doc = reference_filter("Fixed (#{reference}.)") + expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/) + end + + it 'ignores invalid issue IDs' do + invalid = invalidate_reference(reference) + exp = act = "Fixed #{invalid}" + + expect(reference_filter(act).to_html).to eq exp + end + + it 'includes a title attribute' do + doc = reference_filter("Issue #{reference}") + expect(doc.css('a').first.attr('title')).to eq "Issue: #{issue.title}" + end + + it 'escapes the title attribute' do + issue.update_attribute(:title, %{"></a>whatever<a title="}) + + doc = reference_filter("Issue #{reference}") + expect(doc.text).to eq "Issue #{reference}" + end + + it 'includes default classes' do + doc = reference_filter("Issue #{reference}") + expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue' + end + + it 'includes a data-project attribute' do + doc = reference_filter("Issue #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-project') + expect(link.attr('data-project')).to eq project.id.to_s + end + + it 'includes a data-issue attribute' do + doc = reference_filter("See #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-issue') + expect(link.attr('data-issue')).to eq issue.id.to_s + end + + it 'supports an :only_path context' do + doc = reference_filter("Issue #{reference}", only_path: true) + link = doc.css('a').first.attr('href') + + expect(link).not_to match %r(https?://) + expect(link).to eq helper.url_for_issue(issue.iid, project, only_path: true) + end + + it 'adds to the results hash' do + result = reference_pipeline_result("Fixed #{reference}") + expect(result[:references][:issue]).to eq [issue] + end + end + + context 'cross-project reference' do + let(:namespace) { create(:namespace, name: 'cross-reference') } + let(:project2) { create(:empty_project, :public, namespace: namespace) } + let(:issue) { create(:issue, project: project2) } + let(:reference) { issue.to_reference(project) } + + it 'ignores valid references when cross-reference project uses external tracker' do + expect_any_instance_of(Project).to receive(:get_issue). + with(issue.iid).and_return(nil) + + exp = act = "Issue #{reference}" + expect(reference_filter(act).to_html).to eq exp + end + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq helper.url_for_issue(issue.iid, project2) + end + + it 'links with adjacent text' do + doc = reference_filter("Fixed (#{reference}.)") + expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/) + end + + it 'ignores invalid issue IDs on the referenced project' do + exp = act = "Fixed #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq exp + end + + it 'adds to the results hash' do + result = reference_pipeline_result("Fixed #{reference}") + expect(result[:references][:issue]).to eq [issue] + end + end + + context 'cross-project URL reference' do + let(:namespace) { create(:namespace, name: 'cross-reference') } + let(:project2) { create(:empty_project, :public, namespace: namespace) } + let(:issue) { create(:issue, project: project2) } + let(:reference) { helper.url_for_issue(issue.iid, project2) + "#note_123" } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq reference + end + + it 'links with adjacent text' do + doc = reference_filter("Fixed (#{reference}.)") + expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(issue.to_reference(project))} \(comment 123\)<\/a>\.\)/) + end + + it 'adds to the results hash' do + result = reference_pipeline_result("Fixed #{reference}") + expect(result[:references][:issue]).to eq [issue] + end + end + + context 'cross-project reference in link href' do + let(:namespace) { create(:namespace, name: 'cross-reference') } + let(:project2) { create(:empty_project, :public, namespace: namespace) } + let(:issue) { create(:issue, project: project2) } + let(:reference) { %Q{<a href="#{issue.to_reference(project)}">Reference</a>} } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq helper.url_for_issue(issue.iid, project2) + end + + it 'links with adjacent text' do + doc = reference_filter("Fixed (#{reference}.)") + expect(doc.to_html).to match(/\(<a.+>Reference<\/a>\.\)/) + end + + it 'adds to the results hash' do + result = reference_pipeline_result("Fixed #{reference}") + expect(result[:references][:issue]).to eq [issue] + end + end + + context 'cross-project URL in link href' do + let(:namespace) { create(:namespace, name: 'cross-reference') } + let(:project2) { create(:empty_project, :public, namespace: namespace) } + let(:issue) { create(:issue, project: project2) } + let(:reference) { %Q{<a href="#{helper.url_for_issue(issue.iid, project2) + "#note_123"}">Reference</a>} } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq helper.url_for_issue(issue.iid, project2) + "#note_123" + end + + it 'links with adjacent text' do + doc = reference_filter("Fixed (#{reference}.)") + expect(doc.to_html).to match(/\(<a.+>Reference<\/a>\.\)/) + end + + it 'adds to the results hash' do + result = reference_pipeline_result("Fixed #{reference}") + expect(result[:references][:issue]).to eq [issue] + end + end +end diff --git a/spec/lib/gitlab/markdown/filter/label_reference_filter_spec.rb b/spec/lib/gitlab/markdown/filter/label_reference_filter_spec.rb new file mode 100644 index 00000000000..4fcbb329fe4 --- /dev/null +++ b/spec/lib/gitlab/markdown/filter/label_reference_filter_spec.rb @@ -0,0 +1,179 @@ +require 'spec_helper' +require 'html/pipeline' + +describe Gitlab::Markdown::LabelReferenceFilter, lib: true do + include FilterSpecHelper + + let(:project) { create(:empty_project, :public) } + let(:label) { create(:label, project: project) } + let(:reference) { label.to_reference } + + it 'requires project context' do + expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) + end + + %w(pre code a style).each do |elem| + it "ignores valid references contained inside '#{elem}' element" do + exp = act = "<#{elem}>Label #{reference}</#{elem}>" + expect(reference_filter(act).to_html).to eq exp + end + end + + it 'includes default classes' do + doc = reference_filter("Label #{reference}") + expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label' + end + + it 'includes a data-project attribute' do + doc = reference_filter("Label #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-project') + expect(link.attr('data-project')).to eq project.id.to_s + end + + it 'includes a data-label attribute' do + doc = reference_filter("See #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-label') + expect(link.attr('data-label')).to eq label.id.to_s + end + + it 'supports an :only_path context' do + doc = reference_filter("Label #{reference}", only_path: true) + link = doc.css('a').first.attr('href') + + expect(link).not_to match %r(https?://) + expect(link).to eq urls.namespace_project_issues_path(project.namespace, project, label_name: label.name) + end + + it 'adds to the results hash' do + result = reference_pipeline_result("Label #{reference}") + expect(result[:references][:label]).to eq [label] + end + + 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' + end + + it 'includes a style attribute' do + doc = reference_filter("Label #{reference}") + expect(doc.css('a span').first.attr('style')).to match(/\Abackground-color: #\h{6}; color: #\h{6}\z/) + end + end + + context 'Integer-based references' do + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')).to eq urls. + namespace_project_issues_url(project.namespace, project, label_name: label.name) + end + + it 'links with adjacent text' do + doc = reference_filter("Label (#{reference}.)") + expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\))) + end + + it 'ignores invalid label IDs' do + exp = act = "Label #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq exp + end + end + + context 'String-based single-word references' do + let(:label) { create(:label, name: 'gfm', project: project) } + let(:reference) { "#{Label.reference_prefix}#{label.name}" } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')).to eq urls. + namespace_project_issues_url(project.namespace, project, label_name: label.name) + expect(doc.text).to eq 'See gfm' + end + + it 'links with adjacent text' do + doc = reference_filter("Label (#{reference}.)") + expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\))) + end + + it 'ignores invalid label names' do + exp = act = "Label #{Label.reference_prefix}#{label.name.reverse}" + + expect(reference_filter(act).to_html).to eq exp + end + end + + context 'String-based multi-word references in quotes' do + let(:label) { create(:label, name: 'gfm references', project: project) } + let(:reference) { label.to_reference(:name) } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')).to eq urls. + namespace_project_issues_url(project.namespace, project, label_name: label.name) + expect(doc.text).to eq 'See gfm references' + end + + it 'links with adjacent text' do + doc = reference_filter("Label (#{reference}.)") + expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\))) + end + + it 'ignores invalid label names' do + exp = act = %(Label #{Label.reference_prefix}"#{label.name.reverse}") + + expect(reference_filter(act).to_html).to eq exp + end + end + + describe 'edge cases' do + it 'gracefully handles non-references matching the pattern' do + exp = act = '(format nil "~0f" 3.0) ; 3.0' + expect(reference_filter(act).to_html).to eq exp + end + end + + describe 'referencing a label in a link href' do + let(:reference) { %Q{<a href="#{label.to_reference}">Label</a>} } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')).to eq urls. + namespace_project_issues_url(project.namespace, project, label_name: label.name) + end + + it 'links with adjacent text' do + doc = reference_filter("Label (#{reference}.)") + expect(doc.to_html).to match(%r(\(<a.+>Label</a>\.\))) + end + + it 'includes a data-project attribute' do + doc = reference_filter("Label #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-project') + expect(link.attr('data-project')).to eq project.id.to_s + end + + it 'includes a data-label attribute' do + doc = reference_filter("See #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-label') + expect(link.attr('data-label')).to eq label.id.to_s + end + + it 'adds to the results hash' do + result = reference_pipeline_result("Label #{reference}") + expect(result[:references][:label]).to eq [label] + end + end +end diff --git a/spec/lib/gitlab/markdown/filter/merge_request_reference_filter_spec.rb b/spec/lib/gitlab/markdown/filter/merge_request_reference_filter_spec.rb new file mode 100644 index 00000000000..589550e15c4 --- /dev/null +++ b/spec/lib/gitlab/markdown/filter/merge_request_reference_filter_spec.rb @@ -0,0 +1,142 @@ +require 'spec_helper' + +describe Gitlab::Markdown::MergeRequestReferenceFilter, lib: true do + include FilterSpecHelper + + let(:project) { create(:project, :public) } + let(:merge) { create(:merge_request, source_project: project) } + + it 'requires project context' do + expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) + end + + %w(pre code a style).each do |elem| + it "ignores valid references contained inside '#{elem}' element" do + exp = act = "<#{elem}>Merge #{merge.to_reference}</#{elem}>" + expect(reference_filter(act).to_html).to eq exp + end + end + + context 'internal reference' do + let(:reference) { merge.to_reference } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')).to eq urls. + namespace_project_merge_request_url(project.namespace, project, merge) + end + + it 'links with adjacent text' do + doc = reference_filter("Merge (#{reference}.)") + expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/) + end + + it 'ignores invalid merge IDs' do + exp = act = "Merge #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq exp + end + + it 'includes a title attribute' do + doc = reference_filter("Merge #{reference}") + expect(doc.css('a').first.attr('title')).to eq "Merge Request: #{merge.title}" + end + + it 'escapes the title attribute' do + merge.update_attribute(:title, %{"></a>whatever<a title="}) + + doc = reference_filter("Merge #{reference}") + expect(doc.text).to eq "Merge #{reference}" + end + + it 'includes default classes' do + doc = reference_filter("Merge #{reference}") + expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request' + end + + it 'includes a data-project attribute' do + doc = reference_filter("Merge #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-project') + expect(link.attr('data-project')).to eq project.id.to_s + end + + it 'includes a data-merge-request attribute' do + doc = reference_filter("See #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-merge-request') + expect(link.attr('data-merge-request')).to eq merge.id.to_s + end + + it 'supports an :only_path context' do + doc = reference_filter("Merge #{reference}", only_path: true) + link = doc.css('a').first.attr('href') + + expect(link).not_to match %r(https?://) + expect(link).to eq urls.namespace_project_merge_request_url(project.namespace, project, merge, only_path: true) + end + + it 'adds to the results hash' do + result = reference_pipeline_result("Merge #{reference}") + expect(result[:references][:merge_request]).to eq [merge] + end + end + + context 'cross-project reference' do + let(:namespace) { create(:namespace, name: 'cross-reference') } + let(:project2) { create(:project, :public, namespace: namespace) } + let(:merge) { create(:merge_request, source_project: project2) } + let(:reference) { merge.to_reference(project) } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq urls.namespace_project_merge_request_url(project2.namespace, + project, merge) + end + + it 'links with adjacent text' do + doc = reference_filter("Merge (#{reference}.)") + expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/) + end + + it 'ignores invalid merge IDs on the referenced project' do + exp = act = "Merge #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq exp + end + + it 'adds to the results hash' do + result = reference_pipeline_result("Merge #{reference}") + expect(result[:references][:merge_request]).to eq [merge] + end + end + + context 'cross-project URL reference' do + let(:namespace) { create(:namespace, name: 'cross-reference') } + let(:project2) { create(:project, :public, namespace: namespace) } + let(:merge) { create(:merge_request, source_project: project2, target_project: project2) } + let(:reference) { urls.namespace_project_merge_request_url(project2.namespace, project2, merge) + '/diffs#note_123' } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq reference + end + + it 'links with adjacent text' do + doc = reference_filter("Merge (#{reference}.)") + expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(merge.to_reference(project))} \(diffs, comment 123\)<\/a>\.\)/) + end + + it 'adds to the results hash' do + result = reference_pipeline_result("Merge #{reference}") + expect(result[:references][:merge_request]).to eq [merge] + end + end +end diff --git a/spec/lib/gitlab/markdown/filter/redactor_filter_spec.rb b/spec/lib/gitlab/markdown/filter/redactor_filter_spec.rb new file mode 100644 index 00000000000..9e6ee9f0d61 --- /dev/null +++ b/spec/lib/gitlab/markdown/filter/redactor_filter_spec.rb @@ -0,0 +1,89 @@ +require 'spec_helper' + +describe Gitlab::Markdown::RedactorFilter, lib: true do + include ActionView::Helpers::UrlHelper + include FilterSpecHelper + + it 'ignores non-GFM links' do + html = %(See <a href="https://google.com/">Google</a>) + doc = filter(html, current_user: double) + + expect(doc.css('a').length).to eq 1 + end + + def reference_link(data) + link_to('text', '', class: 'gfm', data: data) + end + + context 'with data-project' do + it 'removes unpermitted Project references' do + user = create(:user) + project = create(:empty_project) + + link = reference_link(project: project.id, reference_filter: 'ReferenceFilter') + doc = filter(link, current_user: user) + + expect(doc.css('a').length).to eq 0 + end + + it 'allows permitted Project references' do + user = create(:user) + project = create(:empty_project) + project.team << [user, :master] + + link = reference_link(project: project.id, reference_filter: 'ReferenceFilter') + doc = filter(link, current_user: user) + + expect(doc.css('a').length).to eq 1 + end + + it 'handles invalid Project references' do + link = reference_link(project: 12345, reference_filter: 'ReferenceFilter') + + expect { filter(link) }.not_to raise_error + end + end + + context "for user references" do + + context 'with data-group' do + it 'removes unpermitted Group references' do + user = create(:user) + group = create(:group) + + link = reference_link(group: group.id, reference_filter: 'UserReferenceFilter') + doc = filter(link, current_user: user) + + expect(doc.css('a').length).to eq 0 + end + + it 'allows permitted Group references' do + user = create(:user) + group = create(:group) + group.add_developer(user) + + link = reference_link(group: group.id, reference_filter: 'UserReferenceFilter') + doc = filter(link, current_user: user) + + expect(doc.css('a').length).to eq 1 + end + + it 'handles invalid Group references' do + link = reference_link(group: 12345, reference_filter: 'UserReferenceFilter') + + expect { filter(link) }.not_to raise_error + end + end + + context 'with data-user' do + it 'allows any User reference' do + user = create(:user) + + link = reference_link(user: user.id, reference_filter: 'UserReferenceFilter') + doc = filter(link) + + expect(doc.css('a').length).to eq 1 + end + end + end +end diff --git a/spec/lib/gitlab/markdown/filter/reference_gatherer_filter_spec.rb b/spec/lib/gitlab/markdown/filter/reference_gatherer_filter_spec.rb new file mode 100644 index 00000000000..abfb5ad5e49 --- /dev/null +++ b/spec/lib/gitlab/markdown/filter/reference_gatherer_filter_spec.rb @@ -0,0 +1,87 @@ +require 'spec_helper' + +describe Gitlab::Markdown::ReferenceGathererFilter, lib: true do + include ActionView::Helpers::UrlHelper + include FilterSpecHelper + + def reference_link(data) + link_to('text', '', class: 'gfm', data: data) + end + + context "for issue references" do + + context 'with data-project' do + it 'removes unpermitted Project references' do + user = create(:user) + project = create(:empty_project) + issue = create(:issue, project: project) + + link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter') + result = pipeline_result(link, current_user: user) + + expect(result[:references][:issue]).to be_empty + end + + it 'allows permitted Project references' do + user = create(:user) + project = create(:empty_project) + issue = create(:issue, project: project) + project.team << [user, :master] + + link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter') + result = pipeline_result(link, current_user: user) + + expect(result[:references][:issue]).to eq([issue]) + end + + it 'handles invalid Project references' do + link = reference_link(project: 12345, issue: 12345, reference_filter: 'IssueReferenceFilter') + + expect { pipeline_result(link) }.not_to raise_error + end + end + end + + context "for user references" do + + context 'with data-group' do + it 'removes unpermitted Group references' do + user = create(:user) + group = create(:group) + + link = reference_link(group: group.id, reference_filter: 'UserReferenceFilter') + result = pipeline_result(link, current_user: user) + + expect(result[:references][:user]).to be_empty + end + + it 'allows permitted Group references' do + user = create(:user) + group = create(:group) + group.add_developer(user) + + link = reference_link(group: group.id, reference_filter: 'UserReferenceFilter') + result = pipeline_result(link, current_user: user) + + expect(result[:references][:user]).to eq([user]) + end + + it 'handles invalid Group references' do + link = reference_link(group: 12345, reference_filter: 'UserReferenceFilter') + + expect { pipeline_result(link) }.not_to raise_error + end + end + + context 'with data-user' do + it 'allows any User reference' do + user = create(:user) + + link = reference_link(user: user.id, reference_filter: 'UserReferenceFilter') + result = pipeline_result(link) + + expect(result[:references][:user]).to eq([user]) + end + end + end +end diff --git a/spec/lib/gitlab/markdown/filter/relative_link_filter_spec.rb b/spec/lib/gitlab/markdown/filter/relative_link_filter_spec.rb new file mode 100644 index 00000000000..e0f53e2a533 --- /dev/null +++ b/spec/lib/gitlab/markdown/filter/relative_link_filter_spec.rb @@ -0,0 +1,147 @@ +# encoding: UTF-8 + +require 'spec_helper' + +describe Gitlab::Markdown::RelativeLinkFilter, lib: true do + def filter(doc, contexts = {}) + contexts.reverse_merge!({ + commit: project.commit, + project: project, + project_wiki: project_wiki, + ref: ref, + requested_path: requested_path + }) + + described_class.call(doc, contexts) + end + + def image(path) + %(<img src="#{path}" />) + end + + def link(path) + %(<a href="#{path}">#{path}</a>) + end + + let(:project) { create(:project) } + let(:project_path) { project.path_with_namespace } + let(:ref) { 'markdown' } + let(:project_wiki) { nil } + let(:requested_path) { '/' } + + shared_examples :preserve_unchanged do + it 'does not modify any relative URL in anchor' do + doc = filter(link('README.md')) + expect(doc.at_css('a')['href']).to eq 'README.md' + end + + it 'does not modify any relative URL in image' do + doc = filter(image('files/images/logo-black.png')) + expect(doc.at_css('img')['src']).to eq 'files/images/logo-black.png' + end + end + + shared_examples :relative_to_requested do + it 'rebuilds URL relative to the requested path' do + doc = filter(link('users.md')) + expect(doc.at_css('a')['href']). + to eq "/#{project_path}/blob/#{ref}/doc/api/users.md" + end + end + + context 'with a project_wiki' do + let(:project_wiki) { double('ProjectWiki') } + include_examples :preserve_unchanged + end + + context 'without a repository' do + let(:project) { create(:empty_project) } + include_examples :preserve_unchanged + end + + context 'with an empty repository' do + let(:project) { create(:project_empty_repo) } + include_examples :preserve_unchanged + end + + it 'does not raise an exception on invalid URIs' do + act = link("://foo") + expect { filter(act) }.not_to raise_error + end + + context 'with a valid repository' do + it 'rebuilds relative URL for a file in the repo' do + doc = filter(link('doc/api/README.md')) + expect(doc.at_css('a')['href']). + to eq "/#{project_path}/blob/#{ref}/doc/api/README.md" + end + + it 'rebuilds relative URL for a file in the repo up one directory' do + relative_link = link('../api/README.md') + doc = filter(relative_link, requested_path: 'doc/update/7.14-to-8.0.md') + + expect(doc.at_css('a')['href']). + to eq "/#{project_path}/blob/#{ref}/doc/api/README.md" + end + + it 'rebuilds relative URL for a file in the repo up multiple directories' do + relative_link = link('../../../api/README.md') + doc = filter(relative_link, requested_path: 'doc/foo/bar/baz/README.md') + + expect(doc.at_css('a')['href']). + to eq "/#{project_path}/blob/#{ref}/doc/api/README.md" + end + + it 'rebuilds relative URL for a file in the repo with an anchor' do + doc = filter(link('README.md#section')) + expect(doc.at_css('a')['href']). + to eq "/#{project_path}/blob/#{ref}/README.md#section" + end + + it 'rebuilds relative URL for a directory in the repo' do + doc = filter(link('doc/api/')) + expect(doc.at_css('a')['href']). + to eq "/#{project_path}/tree/#{ref}/doc/api" + end + + it 'rebuilds relative URL for an image in the repo' do + doc = filter(link('files/images/logo-black.png')) + expect(doc.at_css('a')['href']). + to eq "/#{project_path}/raw/#{ref}/files/images/logo-black.png" + end + + it 'does not modify relative URL with an anchor only' do + doc = filter(link('#section-1')) + expect(doc.at_css('a')['href']).to eq '#section-1' + end + + it 'does not modify absolute URL' do + doc = filter(link('http://example.com')) + expect(doc.at_css('a')['href']).to eq 'http://example.com' + end + + it 'supports Unicode filenames' do + path = 'files/images/한글.png' + escaped = Addressable::URI.escape(path) + + # Stub these methods so the file doesn't actually need to be in the repo + allow_any_instance_of(described_class). + to receive(:file_exists?).and_return(true) + allow_any_instance_of(described_class). + to receive(:image?).with(path).and_return(true) + + doc = filter(image(escaped)) + expect(doc.at_css('img')['src']).to match '/raw/' + end + + context 'when requested path is a file in the repo' do + let(:requested_path) { 'doc/api/README.md' } + include_examples :relative_to_requested + end + + context 'when requested path is a directory in the repo' do + let(:requested_path) { 'doc/api' } + include_examples :relative_to_requested + end + end +end diff --git a/spec/lib/gitlab/markdown/filter/sanitization_filter_spec.rb b/spec/lib/gitlab/markdown/filter/sanitization_filter_spec.rb new file mode 100644 index 00000000000..a5e5ee0e08a --- /dev/null +++ b/spec/lib/gitlab/markdown/filter/sanitization_filter_spec.rb @@ -0,0 +1,197 @@ +require 'spec_helper' + +describe Gitlab::Markdown::SanitizationFilter, lib: true do + include FilterSpecHelper + + describe 'default whitelist' do + it 'sanitizes tags that are not whitelisted' do + act = %q{<textarea>no inputs</textarea> and <blink>no blinks</blink>} + exp = 'no inputs and no blinks' + expect(filter(act).to_html).to eq exp + end + + it 'sanitizes tag attributes' do + act = %q{<a href="http://example.com/bar.html" onclick="bar">Text</a>} + exp = %q{<a href="http://example.com/bar.html">Text</a>} + expect(filter(act).to_html).to eq exp + end + + it 'sanitizes javascript in attributes' do + act = %q(<a href="javascript:alert('foo')">Text</a>) + exp = '<a>Text</a>' + expect(filter(act).to_html).to eq exp + end + + it 'allows whitelisted HTML tags from the user' do + exp = act = "<dl>\n<dt>Term</dt>\n<dd>Definition</dd>\n</dl>" + expect(filter(act).to_html).to eq exp + end + + it 'sanitizes `class` attribute on any element' do + act = %q{<strong class="foo">Strong</strong>} + expect(filter(act).to_html).to eq %q{<strong>Strong</strong>} + end + + it 'sanitizes `id` attribute on any element' do + act = %q{<em id="foo">Emphasis</em>} + expect(filter(act).to_html).to eq %q{<em>Emphasis</em>} + end + end + + describe 'custom whitelist' do + it 'customizes the whitelist only once' do + instance = described_class.new('Foo') + 3.times { instance.whitelist } + + expect(instance.whitelist[:transformers].size).to eq 5 + end + + it 'allows syntax highlighting' do + exp = act = %q{<pre class="code highlight white c"><code><span class="k">def</span></code></pre>} + expect(filter(act).to_html).to eq exp + end + + it 'sanitizes `class` attribute from non-highlight spans' do + act = %q{<span class="k">def</span>} + expect(filter(act).to_html).to eq %q{<span>def</span>} + end + + it 'allows `style` attribute on table elements' do + html = <<-HTML.strip_heredoc + <table> + <tr><th style="text-align: center">Head</th></tr> + <tr><td style="text-align: right">Body</th></tr> + </table> + HTML + + doc = filter(html) + + expect(doc.at_css('th')['style']).to eq 'text-align: center' + expect(doc.at_css('td')['style']).to eq 'text-align: right' + end + + it 'allows `span` elements' do + exp = act = %q{<span>Hello</span>} + expect(filter(act).to_html).to eq exp + end + + it 'removes `rel` attribute from `a` elements' do + act = %q{<a href="#" rel="nofollow">Link</a>} + exp = %q{<a href="#">Link</a>} + + expect(filter(act).to_html).to eq exp + end + + # Adapted from the Sanitize test suite: http://git.io/vczrM + protocols = { + 'protocol-based JS injection: simple, no spaces' => { + input: '<a href="javascript:alert(\'XSS\');">foo</a>', + output: '<a>foo</a>' + }, + + 'protocol-based JS injection: simple, spaces before' => { + input: '<a href="javascript :alert(\'XSS\');">foo</a>', + output: '<a>foo</a>' + }, + + 'protocol-based JS injection: simple, spaces after' => { + input: '<a href="javascript: alert(\'XSS\');">foo</a>', + output: '<a>foo</a>' + }, + + 'protocol-based JS injection: simple, spaces before and after' => { + input: '<a href="javascript : alert(\'XSS\');">foo</a>', + output: '<a>foo</a>' + }, + + 'protocol-based JS injection: preceding colon' => { + input: '<a href=":javascript:alert(\'XSS\');">foo</a>', + output: '<a>foo</a>' + }, + + 'protocol-based JS injection: UTF-8 encoding' => { + input: '<a href="javascript:">foo</a>', + output: '<a>foo</a>' + }, + + 'protocol-based JS injection: long UTF-8 encoding' => { + input: '<a href="javascript:">foo</a>', + output: '<a>foo</a>' + }, + + 'protocol-based JS injection: long UTF-8 encoding without semicolons' => { + input: '<a href=javascript:alert('XSS')>foo</a>', + output: '<a>foo</a>' + }, + + 'protocol-based JS injection: hex encoding' => { + input: '<a href="javascript:">foo</a>', + output: '<a>foo</a>' + }, + + 'protocol-based JS injection: long hex encoding' => { + input: '<a href="javascript:">foo</a>', + output: '<a>foo</a>' + }, + + 'protocol-based JS injection: hex encoding without semicolons' => { + input: '<a href=javascript:alert('XSS')>foo</a>', + output: '<a>foo</a>' + }, + + 'protocol-based JS injection: null char' => { + input: "<a href=java\0script:alert(\"XSS\")>foo</a>", + output: '<a href="java"></a>' + }, + + 'protocol-based JS injection: spaces and entities' => { + input: '<a href="  javascript:alert(\'XSS\');">foo</a>', + output: '<a href="">foo</a>' + }, + } + + protocols.each do |name, data| + it "handles #{name}" do + doc = filter(data[:input]) + + expect(doc.to_html).to eq data[:output] + end + end + + it 'allows non-standard anchor schemes' do + exp = %q{<a href="irc://irc.freenode.net/git">IRC</a>} + act = filter(exp) + + expect(act.to_html).to eq exp + end + + it 'allows relative links' do + exp = %q{<a href="foo/bar.md">foo/bar.md</a>} + act = filter(exp) + + expect(act.to_html).to eq exp + end + end + + context 'when inline_sanitization is true' do + it 'uses a stricter whitelist' do + doc = filter('<h1>Description</h1>', inline_sanitization: true) + expect(doc.to_html.strip).to eq 'Description' + end + + %w(pre code img ol ul li).each do |elem| + it "removes '#{elem}' elements" do + act = "<#{elem}>Description</#{elem}>" + expect(filter(act, inline_sanitization: true).to_html.strip). + to eq 'Description' + end + end + + %w(b i strong em a ins del sup sub p).each do |elem| + it "still allows '#{elem}' elements" do + exp = act = "<#{elem}>Description</#{elem}>" + expect(filter(act, inline_sanitization: true).to_html).to eq exp + end + end + end +end diff --git a/spec/lib/gitlab/markdown/filter/snippet_reference_filter_spec.rb b/spec/lib/gitlab/markdown/filter/snippet_reference_filter_spec.rb new file mode 100644 index 00000000000..51526b58597 --- /dev/null +++ b/spec/lib/gitlab/markdown/filter/snippet_reference_filter_spec.rb @@ -0,0 +1,146 @@ +require 'spec_helper' + +describe Gitlab::Markdown::SnippetReferenceFilter, lib: true do + include FilterSpecHelper + + let(:project) { create(:empty_project, :public) } + let(:snippet) { create(:project_snippet, project: project) } + let(:reference) { snippet.to_reference } + + it 'requires project context' do + expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) + end + + %w(pre code a style).each do |elem| + it "ignores valid references contained inside '#{elem}' element" do + exp = act = "<#{elem}>Snippet #{reference}</#{elem}>" + expect(reference_filter(act).to_html).to eq exp + end + end + + context 'internal reference' do + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')).to eq urls. + namespace_project_snippet_url(project.namespace, project, snippet) + end + + it 'links with adjacent text' do + doc = reference_filter("Snippet (#{reference}.)") + expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/) + end + + it 'ignores invalid snippet IDs' do + exp = act = "Snippet #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq exp + end + + it 'includes a title attribute' do + doc = reference_filter("Snippet #{reference}") + expect(doc.css('a').first.attr('title')).to eq "Snippet: #{snippet.title}" + end + + it 'escapes the title attribute' do + snippet.update_attribute(:title, %{"></a>whatever<a title="}) + + doc = reference_filter("Snippet #{reference}") + expect(doc.text).to eq "Snippet #{reference}" + end + + it 'includes default classes' do + doc = reference_filter("Snippet #{reference}") + expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet' + end + + it 'includes a data-project attribute' do + doc = reference_filter("Snippet #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-project') + expect(link.attr('data-project')).to eq project.id.to_s + end + + it 'includes a data-snippet attribute' do + doc = reference_filter("See #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-snippet') + expect(link.attr('data-snippet')).to eq snippet.id.to_s + end + + it 'supports an :only_path context' do + doc = reference_filter("Snippet #{reference}", only_path: true) + link = doc.css('a').first.attr('href') + + expect(link).not_to match %r(https?://) + expect(link).to eq urls.namespace_project_snippet_url(project.namespace, project, snippet, only_path: true) + end + + it 'adds to the results hash' do + result = reference_pipeline_result("Snippet #{reference}") + expect(result[:references][:snippet]).to eq [snippet] + end + end + + context 'cross-project reference' do + let(:namespace) { create(:namespace, name: 'cross-reference') } + let(:project2) { create(:empty_project, :public, namespace: namespace) } + let(:snippet) { create(:project_snippet, project: project2) } + let(:reference) { snippet.to_reference(project) } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet) + end + + it 'links with adjacent text' do + doc = reference_filter("See (#{reference}.)") + expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/) + end + + it 'ignores invalid snippet IDs on the referenced project' do + exp = act = "See #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq exp + end + + it 'adds to the results hash' do + result = reference_pipeline_result("Snippet #{reference}") + expect(result[:references][:snippet]).to eq [snippet] + end + end + + context 'cross-project URL reference' do + let(:namespace) { create(:namespace, name: 'cross-reference') } + let(:project2) { create(:empty_project, :public, namespace: namespace) } + let(:snippet) { create(:project_snippet, project: project2) } + let(:reference) { urls.namespace_project_snippet_url(project2.namespace, project2, snippet) } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet) + end + + it 'links with adjacent text' do + doc = reference_filter("See (#{reference}.)") + expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(snippet.to_reference(project))}<\/a>\.\)/) + end + + it 'ignores invalid snippet IDs on the referenced project' do + act = "See #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to match(/<a.+>#{Regexp.escape(invalidate_reference(reference))}<\/a>/) + end + + it 'adds to the results hash' do + result = reference_pipeline_result("Snippet #{reference}") + expect(result[:references][:snippet]).to eq [snippet] + end + end +end diff --git a/spec/lib/gitlab/markdown/filter/syntax_highlight_filter_spec.rb b/spec/lib/gitlab/markdown/filter/syntax_highlight_filter_spec.rb new file mode 100644 index 00000000000..8b76048f3e3 --- /dev/null +++ b/spec/lib/gitlab/markdown/filter/syntax_highlight_filter_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe Gitlab::Markdown::SyntaxHighlightFilter, lib: true do + include FilterSpecHelper + + it 'highlights valid code blocks' do + result = filter('<pre><code>def fun end</code>') + expect(result.to_html).to eq("<pre class=\"code highlight js-syntax-highlight plaintext\"><code>def fun end</code></pre>\n") + end + + it 'passes through invalid code blocks' do + allow_any_instance_of(described_class).to receive(:block_code).and_raise(StandardError) + + result = filter('<pre><code>This is a test</code></pre>') + expect(result.to_html).to eq('<pre>This is a test</pre>') + end +end diff --git a/spec/lib/gitlab/markdown/filter/table_of_contents_filter_spec.rb b/spec/lib/gitlab/markdown/filter/table_of_contents_filter_spec.rb new file mode 100644 index 00000000000..c8c79c41847 --- /dev/null +++ b/spec/lib/gitlab/markdown/filter/table_of_contents_filter_spec.rb @@ -0,0 +1,97 @@ +# encoding: UTF-8 + +require 'spec_helper' + +describe Gitlab::Markdown::TableOfContentsFilter, lib: true do + include FilterSpecHelper + + def header(level, text) + "<h#{level}>#{text}</h#{level}>\n" + end + + it 'does nothing when :no_header_anchors is truthy' do + exp = act = header(1, 'Header') + expect(filter(act, no_header_anchors: 1).to_html).to eq exp + end + + it 'does nothing with empty headers' do + exp = act = header(1, nil) + expect(filter(act).to_html).to eq exp + end + + 1.upto(6) do |i| + it "processes h#{i} elements" do + html = header(i, "Header #{i}") + doc = filter(html) + + expect(doc.css("h#{i} a").first.attr('id')).to eq "header-#{i}" + end + end + + describe 'anchor tag' do + it 'has an `anchor` class' do + doc = filter(header(1, 'Header')) + expect(doc.css('h1 a').first.attr('class')).to eq 'anchor' + end + + it 'links to the id' do + doc = filter(header(1, 'Header')) + expect(doc.css('h1 a').first.attr('href')).to eq '#header' + end + + describe 'generated IDs' do + it 'translates spaces to dashes' do + doc = filter(header(1, 'This header has spaces in it')) + expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-has-spaces-in-it' + end + + it 'squeezes multiple spaces and dashes' do + doc = filter(header(1, 'This---header is poorly-formatted')) + expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-is-poorly-formatted' + end + + it 'removes punctuation' do + doc = filter(header(1, "This, header! is, filled. with @ punctuation?")) + expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-is-filled-with-punctuation' + end + + it 'appends a unique number to duplicates' do + doc = filter(header(1, 'One') + header(2, 'One')) + + expect(doc.css('h1 a').first.attr('id')).to eq 'one' + expect(doc.css('h2 a').first.attr('id')).to eq 'one-1' + end + + it 'supports Unicode' do + doc = filter(header(1, '한글')) + expect(doc.css('h1 a').first.attr('id')).to eq '한글' + expect(doc.css('h1 a').first.attr('href')).to eq '#한글' + end + end + end + + describe 'result' do + def result(html) + HTML::Pipeline.new([described_class]).call(html) + end + + let(:results) { result(header(1, 'Header 1') + header(2, 'Header 2')) } + let(:doc) { Nokogiri::XML::DocumentFragment.parse(results[:toc]) } + + it 'is contained within a `ul` element' do + expect(doc.children.first.name).to eq 'ul' + expect(doc.children.first.attr('class')).to eq 'section-nav' + end + + it 'contains an `li` element for each header' do + expect(doc.css('li').length).to eq 2 + + links = doc.css('li a') + + expect(links.first.attr('href')).to eq '#header-1' + expect(links.first.text).to eq 'Header 1' + expect(links.last.attr('href')).to eq '#header-2' + expect(links.last.text).to eq 'Header 2' + end + end +end diff --git a/spec/lib/gitlab/markdown/filter/task_list_filter_spec.rb b/spec/lib/gitlab/markdown/filter/task_list_filter_spec.rb new file mode 100644 index 00000000000..1b1714ef882 --- /dev/null +++ b/spec/lib/gitlab/markdown/filter/task_list_filter_spec.rb @@ -0,0 +1,10 @@ +require 'spec_helper' + +describe Gitlab::Markdown::TaskListFilter, lib: true do + include FilterSpecHelper + + it 'does not apply `task-list` class to non-task lists' do + exp = act = %(<ul><li>Item</li></ul>) + expect(filter(act).to_html).to eq exp + end +end diff --git a/spec/lib/gitlab/markdown/filter/upload_link_filter_spec.rb b/spec/lib/gitlab/markdown/filter/upload_link_filter_spec.rb new file mode 100644 index 00000000000..38a007b5bee --- /dev/null +++ b/spec/lib/gitlab/markdown/filter/upload_link_filter_spec.rb @@ -0,0 +1,73 @@ +# encoding: UTF-8 + +require 'spec_helper' + +describe Gitlab::Markdown::UploadLinkFilter, lib: true do + def filter(doc, contexts = {}) + contexts.reverse_merge!({ + project: project + }) + + described_class.call(doc, contexts) + end + + def image(path) + %(<img src="#{path}" />) + end + + def link(path) + %(<a href="#{path}">#{path}</a>) + end + + let(:project) { create(:project) } + + shared_examples :preserve_unchanged do + it 'does not modify any relative URL in anchor' do + doc = filter(link('README.md')) + expect(doc.at_css('a')['href']).to eq 'README.md' + end + + it 'does not modify any relative URL in image' do + doc = filter(image('files/images/logo-black.png')) + expect(doc.at_css('img')['src']).to eq 'files/images/logo-black.png' + end + end + + it 'does not raise an exception on invalid URIs' do + act = link("://foo") + expect { filter(act) }.not_to raise_error + end + + context 'with a valid repository' do + it 'rebuilds relative URL for a link' do + doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')) + expect(doc.at_css('a')['href']). + to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg" + end + + it 'rebuilds relative URL for an image' do + doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')) + expect(doc.at_css('a')['href']). + to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg" + end + + it 'does not modify absolute URL' do + doc = filter(link('http://example.com')) + expect(doc.at_css('a')['href']).to eq 'http://example.com' + end + + it 'supports Unicode filenames' do + path = '/uploads/한글.png' + escaped = Addressable::URI.escape(path) + + # Stub these methods so the file doesn't actually need to be in the repo + allow_any_instance_of(described_class). + to receive(:file_exists?).and_return(true) + allow_any_instance_of(described_class). + to receive(:image?).with(path).and_return(true) + + doc = filter(image(escaped)) + expect(doc.at_css('img')['src']).to match "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/%ED%95%9C%EA%B8%80.png" + end + end +end diff --git a/spec/lib/gitlab/markdown/filter/user_reference_filter_spec.rb b/spec/lib/gitlab/markdown/filter/user_reference_filter_spec.rb new file mode 100644 index 00000000000..277037cf68a --- /dev/null +++ b/spec/lib/gitlab/markdown/filter/user_reference_filter_spec.rb @@ -0,0 +1,147 @@ +require 'spec_helper' + +describe Gitlab::Markdown::UserReferenceFilter, lib: true do + include FilterSpecHelper + + let(:project) { create(:empty_project, :public) } + let(:user) { create(:user) } + let(:reference) { user.to_reference } + + it 'requires project context' do + expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) + end + + it 'ignores invalid users' do + exp = act = "Hey #{invalidate_reference(reference)}" + expect(reference_filter(act).to_html).to eq(exp) + end + + %w(pre code a style).each do |elem| + it "ignores valid references contained inside '#{elem}' element" do + exp = act = "<#{elem}>Hey #{reference}</#{elem}>" + expect(reference_filter(act).to_html).to eq exp + end + end + + context 'mentioning @all' do + let(:reference) { User.reference_prefix + 'all' } + + before do + project.team << [project.creator, :developer] + end + + it 'supports a special @all mention' do + doc = reference_filter("Hey #{reference}") + expect(doc.css('a').length).to eq 1 + expect(doc.css('a').first.attr('href')) + .to eq urls.namespace_project_url(project.namespace, project) + end + + it 'adds to the results hash' do + result = reference_pipeline_result("Hey #{reference}") + expect(result[:references][:user]).to eq [project.creator] + end + end + + context 'mentioning a user' do + it 'links to a User' do + doc = reference_filter("Hey #{reference}") + expect(doc.css('a').first.attr('href')).to eq urls.user_url(user) + end + + it 'links to a User with a period' do + user = create(:user, name: 'alphA.Beta') + + doc = reference_filter("Hey #{user.to_reference}") + expect(doc.css('a').length).to eq 1 + end + + it 'links to a User with an underscore' do + user = create(:user, name: 'ping_pong_king') + + doc = reference_filter("Hey #{user.to_reference}") + expect(doc.css('a').length).to eq 1 + end + + it 'includes a data-user attribute' do + doc = reference_filter("Hey #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-user') + expect(link.attr('data-user')).to eq user.namespace.owner_id.to_s + end + + it 'adds to the results hash' do + result = reference_pipeline_result("Hey #{reference}") + expect(result[:references][:user]).to eq [user] + end + end + + context 'mentioning a group' do + let(:group) { create(:group) } + let(:reference) { group.to_reference } + + it 'links to the Group' do + doc = reference_filter("Hey #{reference}") + expect(doc.css('a').first.attr('href')).to eq urls.group_url(group) + end + + it 'includes a data-group attribute' do + doc = reference_filter("Hey #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-group') + expect(link.attr('data-group')).to eq group.id.to_s + end + + it 'adds to the results hash' do + result = reference_pipeline_result("Hey #{reference}") + expect(result[:references][:user]).to eq group.users + end + end + + it 'links with adjacent text' do + doc = reference_filter("Mention me (#{reference}.)") + expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/) + end + + it 'includes default classes' do + doc = reference_filter("Hey #{reference}") + expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member' + end + + it 'supports an :only_path context' do + doc = reference_filter("Hey #{reference}", only_path: true) + link = doc.css('a').first.attr('href') + + expect(link).not_to match %r(https?://) + expect(link).to eq urls.user_path(user) + end + + context 'referencing a user in a link href' do + let(:reference) { %Q{<a href="#{user.to_reference}">User</a>} } + + it 'links to a User' do + doc = reference_filter("Hey #{reference}") + expect(doc.css('a').first.attr('href')).to eq urls.user_url(user) + end + + it 'links with adjacent text' do + doc = reference_filter("Mention me (#{reference}.)") + expect(doc.to_html).to match(/\(<a.+>User<\/a>\.\)/) + end + + it 'includes a data-user attribute' do + doc = reference_filter("Hey #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-user') + expect(link.attr('data-user')).to eq user.namespace.owner_id.to_s + end + + it 'adds to the results hash' do + result = reference_pipeline_result("Hey #{reference}") + expect(result[:references][:user]).to eq [user] + end + end +end diff --git a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb deleted file mode 100644 index 94c80ae6611..00000000000 --- a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb +++ /dev/null @@ -1,139 +0,0 @@ -require 'spec_helper' - -module Gitlab::Markdown - describe IssueReferenceFilter do - include FilterSpecHelper - - def helper - IssuesHelper - end - - let(:project) { create(:empty_project, :public) } - let(:issue) { create(:issue, project: project) } - - it 'requires project context' do - expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) - end - - %w(pre code a style).each do |elem| - it "ignores valid references contained inside '#{elem}' element" do - exp = act = "<#{elem}>Issue #{issue.to_reference}</#{elem}>" - expect(filter(act).to_html).to eq exp - end - end - - context 'internal reference' do - let(:reference) { issue.to_reference } - - it 'ignores valid references when using non-default tracker' do - expect(project).to receive(:get_issue).with(issue.iid).and_return(nil) - - exp = act = "Issue #{reference}" - expect(filter(act).to_html).to eq exp - end - - it 'links to a valid reference' do - doc = filter("Fixed #{reference}") - - expect(doc.css('a').first.attr('href')). - to eq helper.url_for_issue(issue.iid, project) - end - - it 'links with adjacent text' do - doc = filter("Fixed (#{reference}.)") - expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/) - end - - it 'ignores invalid issue IDs' do - invalid = invalidate_reference(reference) - exp = act = "Fixed #{invalid}" - - expect(filter(act).to_html).to eq exp - end - - it 'includes a title attribute' do - doc = filter("Issue #{reference}") - expect(doc.css('a').first.attr('title')).to eq "Issue: #{issue.title}" - end - - it 'escapes the title attribute' do - issue.update_attribute(:title, %{"></a>whatever<a title="}) - - doc = filter("Issue #{reference}") - expect(doc.text).to eq "Issue #{reference}" - end - - it 'includes default classes' do - doc = filter("Issue #{reference}") - expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue' - end - - it 'includes a data-project attribute' do - doc = filter("Issue #{reference}") - link = doc.css('a').first - - expect(link).to have_attribute('data-project') - expect(link.attr('data-project')).to eq project.id.to_s - end - - it 'includes a data-issue attribute' do - doc = filter("See #{reference}") - link = doc.css('a').first - - expect(link).to have_attribute('data-issue') - expect(link.attr('data-issue')).to eq issue.id.to_s - end - - it 'supports an :only_path context' do - doc = filter("Issue #{reference}", only_path: true) - link = doc.css('a').first.attr('href') - - expect(link).not_to match %r(https?://) - expect(link).to eq helper.url_for_issue(issue.iid, project, only_path: true) - end - - it 'adds to the results hash' do - result = reference_pipeline_result("Fixed #{reference}") - expect(result[:references][:issue]).to eq [issue] - end - end - - context 'cross-project reference' do - let(:namespace) { create(:namespace, name: 'cross-reference') } - let(:project2) { create(:empty_project, :public, namespace: namespace) } - let(:issue) { create(:issue, project: project2) } - let(:reference) { issue.to_reference(project) } - - it 'ignores valid references when cross-reference project uses external tracker' do - expect_any_instance_of(Project).to receive(:get_issue). - with(issue.iid).and_return(nil) - - exp = act = "Issue #{reference}" - expect(filter(act).to_html).to eq exp - end - - it 'links to a valid reference' do - doc = filter("See #{reference}") - - expect(doc.css('a').first.attr('href')). - to eq helper.url_for_issue(issue.iid, project2) - end - - it 'links with adjacent text' do - doc = filter("Fixed (#{reference}.)") - expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/) - end - - it 'ignores invalid issue IDs on the referenced project' do - exp = act = "Fixed #{invalidate_reference(reference)}" - - expect(filter(act).to_html).to eq exp - end - - it 'adds to the results hash' do - result = reference_pipeline_result("Fixed #{reference}") - expect(result[:references][:issue]).to eq [issue] - end - end - end -end diff --git a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb deleted file mode 100644 index fc21b65a843..00000000000 --- a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb +++ /dev/null @@ -1,144 +0,0 @@ -require 'spec_helper' -require 'html/pipeline' - -module Gitlab::Markdown - describe LabelReferenceFilter do - include FilterSpecHelper - - let(:project) { create(:empty_project, :public) } - let(:label) { create(:label, project: project) } - let(:reference) { label.to_reference } - - it 'requires project context' do - expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) - end - - %w(pre code a style).each do |elem| - it "ignores valid references contained inside '#{elem}' element" do - exp = act = "<#{elem}>Label #{reference}</#{elem}>" - expect(filter(act).to_html).to eq exp - end - end - - it 'includes default classes' do - doc = filter("Label #{reference}") - expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label' - end - - it 'includes a data-project attribute' do - doc = filter("Label #{reference}") - link = doc.css('a').first - - expect(link).to have_attribute('data-project') - expect(link.attr('data-project')).to eq project.id.to_s - end - - it 'includes a data-label attribute' do - doc = filter("See #{reference}") - link = doc.css('a').first - - expect(link).to have_attribute('data-label') - expect(link.attr('data-label')).to eq label.id.to_s - end - - it 'supports an :only_path context' do - doc = filter("Label #{reference}", only_path: true) - link = doc.css('a').first.attr('href') - - expect(link).not_to match %r(https?://) - expect(link).to eq urls.namespace_project_issues_path(project.namespace, project, label_name: label.name) - end - - it 'adds to the results hash' do - result = reference_pipeline_result("Label #{reference}") - expect(result[:references][:label]).to eq [label] - end - - describe 'label span element' do - it 'includes default classes' do - doc = filter("Label #{reference}") - expect(doc.css('a span').first.attr('class')).to eq 'label color-label' - end - - it 'includes a style attribute' do - doc = filter("Label #{reference}") - expect(doc.css('a span').first.attr('style')).to match(/\Abackground-color: #\h{6}; color: #\h{6}\z/) - end - end - - context 'Integer-based references' do - it 'links to a valid reference' do - doc = filter("See #{reference}") - - expect(doc.css('a').first.attr('href')).to eq urls. - namespace_project_issues_url(project.namespace, project, label_name: label.name) - end - - it 'links with adjacent text' do - doc = filter("Label (#{reference}.)") - expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\))) - end - - it 'ignores invalid label IDs' do - exp = act = "Label #{invalidate_reference(reference)}" - - expect(filter(act).to_html).to eq exp - end - end - - context 'String-based single-word references' do - let(:label) { create(:label, name: 'gfm', project: project) } - let(:reference) { "#{Label.reference_prefix}#{label.name}" } - - it 'links to a valid reference' do - doc = filter("See #{reference}") - - expect(doc.css('a').first.attr('href')).to eq urls. - namespace_project_issues_url(project.namespace, project, label_name: label.name) - expect(doc.text).to eq 'See gfm' - end - - it 'links with adjacent text' do - doc = filter("Label (#{reference}.)") - expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\))) - end - - it 'ignores invalid label names' do - exp = act = "Label #{Label.reference_prefix}#{label.name.reverse}" - - expect(filter(act).to_html).to eq exp - end - end - - context 'String-based multi-word references in quotes' do - let(:label) { create(:label, name: 'gfm references', project: project) } - let(:reference) { label.to_reference(:name) } - - it 'links to a valid reference' do - doc = filter("See #{reference}") - - expect(doc.css('a').first.attr('href')).to eq urls. - namespace_project_issues_url(project.namespace, project, label_name: label.name) - expect(doc.text).to eq 'See gfm references' - end - - it 'links with adjacent text' do - doc = filter("Label (#{reference}.)") - expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\))) - end - - it 'ignores invalid label names' do - exp = act = %(Label #{Label.reference_prefix}"#{label.name.reverse}") - - expect(filter(act).to_html).to eq exp - end - end - - describe 'edge cases' do - it 'gracefully handles non-references matching the pattern' do - exp = act = '(format nil "~0f" 3.0) ; 3.0' - expect(filter(act).to_html).to eq exp - end - end - end -end diff --git a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb deleted file mode 100644 index 3ef6cdfff33..00000000000 --- a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb +++ /dev/null @@ -1,120 +0,0 @@ -require 'spec_helper' - -module Gitlab::Markdown - describe MergeRequestReferenceFilter do - include FilterSpecHelper - - let(:project) { create(:project, :public) } - let(:merge) { create(:merge_request, source_project: project) } - - it 'requires project context' do - expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) - end - - %w(pre code a style).each do |elem| - it "ignores valid references contained inside '#{elem}' element" do - exp = act = "<#{elem}>Merge #{merge.to_reference}</#{elem}>" - expect(filter(act).to_html).to eq exp - end - end - - context 'internal reference' do - let(:reference) { merge.to_reference } - - it 'links to a valid reference' do - doc = filter("See #{reference}") - - expect(doc.css('a').first.attr('href')).to eq urls. - namespace_project_merge_request_url(project.namespace, project, merge) - end - - it 'links with adjacent text' do - doc = filter("Merge (#{reference}.)") - expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/) - end - - it 'ignores invalid merge IDs' do - exp = act = "Merge #{invalidate_reference(reference)}" - - expect(filter(act).to_html).to eq exp - end - - it 'includes a title attribute' do - doc = filter("Merge #{reference}") - expect(doc.css('a').first.attr('title')).to eq "Merge Request: #{merge.title}" - end - - it 'escapes the title attribute' do - merge.update_attribute(:title, %{"></a>whatever<a title="}) - - doc = filter("Merge #{reference}") - expect(doc.text).to eq "Merge #{reference}" - end - - it 'includes default classes' do - doc = filter("Merge #{reference}") - expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request' - end - - it 'includes a data-project attribute' do - doc = filter("Merge #{reference}") - link = doc.css('a').first - - expect(link).to have_attribute('data-project') - expect(link.attr('data-project')).to eq project.id.to_s - end - - it 'includes a data-merge-request attribute' do - doc = filter("See #{reference}") - link = doc.css('a').first - - expect(link).to have_attribute('data-merge-request') - expect(link.attr('data-merge-request')).to eq merge.id.to_s - end - - it 'supports an :only_path context' do - doc = filter("Merge #{reference}", only_path: true) - link = doc.css('a').first.attr('href') - - expect(link).not_to match %r(https?://) - expect(link).to eq urls.namespace_project_merge_request_url(project.namespace, project, merge, only_path: true) - end - - it 'adds to the results hash' do - result = reference_pipeline_result("Merge #{reference}") - expect(result[:references][:merge_request]).to eq [merge] - end - end - - context 'cross-project reference' do - let(:namespace) { create(:namespace, name: 'cross-reference') } - let(:project2) { create(:project, :public, namespace: namespace) } - let(:merge) { create(:merge_request, source_project: project2) } - let(:reference) { merge.to_reference(project) } - - it 'links to a valid reference' do - doc = filter("See #{reference}") - - expect(doc.css('a').first.attr('href')). - to eq urls.namespace_project_merge_request_url(project2.namespace, - project, merge) - end - - it 'links with adjacent text' do - doc = filter("Merge (#{reference}.)") - expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/) - end - - it 'ignores invalid merge IDs on the referenced project' do - exp = act = "Merge #{invalidate_reference(reference)}" - - expect(filter(act).to_html).to eq exp - end - - it 'adds to the results hash' do - result = reference_pipeline_result("Merge #{reference}") - expect(result[:references][:merge_request]).to eq [merge] - end - end - end -end diff --git a/spec/lib/gitlab/markdown/redactor_filter_spec.rb b/spec/lib/gitlab/markdown/redactor_filter_spec.rb deleted file mode 100644 index eea3f1cf370..00000000000 --- a/spec/lib/gitlab/markdown/redactor_filter_spec.rb +++ /dev/null @@ -1,91 +0,0 @@ -require 'spec_helper' - -module Gitlab::Markdown - describe RedactorFilter do - include ActionView::Helpers::UrlHelper - include FilterSpecHelper - - it 'ignores non-GFM links' do - html = %(See <a href="https://google.com/">Google</a>) - doc = filter(html, current_user: double) - - expect(doc.css('a').length).to eq 1 - end - - def reference_link(data) - link_to('text', '', class: 'gfm', data: data) - end - - context 'with data-project' do - it 'removes unpermitted Project references' do - user = create(:user) - project = create(:empty_project) - - link = reference_link(project: project.id, reference_filter: Gitlab::Markdown::ReferenceFilter.name) - doc = filter(link, current_user: user) - - expect(doc.css('a').length).to eq 0 - end - - it 'allows permitted Project references' do - user = create(:user) - project = create(:empty_project) - project.team << [user, :master] - - link = reference_link(project: project.id, reference_filter: Gitlab::Markdown::ReferenceFilter.name) - doc = filter(link, current_user: user) - - expect(doc.css('a').length).to eq 1 - end - - it 'handles invalid Project references' do - link = reference_link(project: 12345, reference_filter: Gitlab::Markdown::ReferenceFilter.name) - - expect { filter(link) }.not_to raise_error - end - end - - context "for user references" do - - context 'with data-group' do - it 'removes unpermitted Group references' do - user = create(:user) - group = create(:group) - - link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name) - doc = filter(link, current_user: user) - - expect(doc.css('a').length).to eq 0 - end - - it 'allows permitted Group references' do - user = create(:user) - group = create(:group) - group.add_developer(user) - - link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name) - doc = filter(link, current_user: user) - - expect(doc.css('a').length).to eq 1 - end - - it 'handles invalid Group references' do - link = reference_link(group: 12345, reference_filter: Gitlab::Markdown::UserReferenceFilter.name) - - expect { filter(link) }.not_to raise_error - end - end - - context 'with data-user' do - it 'allows any User reference' do - user = create(:user) - - link = reference_link(user: user.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name) - doc = filter(link) - - expect(doc.css('a').length).to eq 1 - end - end - end - end -end diff --git a/spec/lib/gitlab/markdown/reference_gatherer_filter_spec.rb b/spec/lib/gitlab/markdown/reference_gatherer_filter_spec.rb deleted file mode 100644 index 4fa473ad191..00000000000 --- a/spec/lib/gitlab/markdown/reference_gatherer_filter_spec.rb +++ /dev/null @@ -1,89 +0,0 @@ -require 'spec_helper' - -module Gitlab::Markdown - describe ReferenceGathererFilter do - include ActionView::Helpers::UrlHelper - include FilterSpecHelper - - def reference_link(data) - link_to('text', '', class: 'gfm', data: data) - end - - context "for issue references" do - - context 'with data-project' do - it 'removes unpermitted Project references' do - user = create(:user) - project = create(:empty_project) - issue = create(:issue, project: project) - - link = reference_link(project: project.id, issue: issue.id, reference_filter: Gitlab::Markdown::IssueReferenceFilter.name) - result = pipeline_result(link, current_user: user) - - expect(result[:references][:issue]).to be_empty - end - - it 'allows permitted Project references' do - user = create(:user) - project = create(:empty_project) - issue = create(:issue, project: project) - project.team << [user, :master] - - link = reference_link(project: project.id, issue: issue.id, reference_filter: Gitlab::Markdown::IssueReferenceFilter.name) - result = pipeline_result(link, current_user: user) - - expect(result[:references][:issue]).to eq([issue]) - end - - it 'handles invalid Project references' do - link = reference_link(project: 12345, issue: 12345, reference_filter: Gitlab::Markdown::IssueReferenceFilter.name) - - expect { pipeline_result(link) }.not_to raise_error - end - end - end - - context "for user references" do - - context 'with data-group' do - it 'removes unpermitted Group references' do - user = create(:user) - group = create(:group) - - link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name) - result = pipeline_result(link, current_user: user) - - expect(result[:references][:user]).to be_empty - end - - it 'allows permitted Group references' do - user = create(:user) - group = create(:group) - group.add_developer(user) - - link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name) - result = pipeline_result(link, current_user: user) - - expect(result[:references][:user]).to eq([user]) - end - - it 'handles invalid Group references' do - link = reference_link(group: 12345, reference_filter: Gitlab::Markdown::UserReferenceFilter.name) - - expect { pipeline_result(link) }.not_to raise_error - end - end - - context 'with data-user' do - it 'allows any User reference' do - user = create(:user) - - link = reference_link(user: user.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name) - result = pipeline_result(link) - - expect(result[:references][:user]).to eq([user]) - end - end - end - end -end diff --git a/spec/lib/gitlab/markdown/relative_link_filter_spec.rb b/spec/lib/gitlab/markdown/relative_link_filter_spec.rb deleted file mode 100644 index 027336ceb73..00000000000 --- a/spec/lib/gitlab/markdown/relative_link_filter_spec.rb +++ /dev/null @@ -1,149 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' - -module Gitlab::Markdown - describe RelativeLinkFilter do - def filter(doc, contexts = {}) - contexts.reverse_merge!({ - commit: project.commit, - project: project, - project_wiki: project_wiki, - ref: ref, - requested_path: requested_path - }) - - described_class.call(doc, contexts) - end - - def image(path) - %(<img src="#{path}" />) - end - - def link(path) - %(<a href="#{path}">#{path}</a>) - end - - let(:project) { create(:project) } - let(:project_path) { project.path_with_namespace } - let(:ref) { 'markdown' } - let(:project_wiki) { nil } - let(:requested_path) { '/' } - - shared_examples :preserve_unchanged do - it 'does not modify any relative URL in anchor' do - doc = filter(link('README.md')) - expect(doc.at_css('a')['href']).to eq 'README.md' - end - - it 'does not modify any relative URL in image' do - doc = filter(image('files/images/logo-black.png')) - expect(doc.at_css('img')['src']).to eq 'files/images/logo-black.png' - end - end - - shared_examples :relative_to_requested do - it 'rebuilds URL relative to the requested path' do - doc = filter(link('users.md')) - expect(doc.at_css('a')['href']). - to eq "/#{project_path}/blob/#{ref}/doc/api/users.md" - end - end - - context 'with a project_wiki' do - let(:project_wiki) { double('ProjectWiki') } - include_examples :preserve_unchanged - end - - context 'without a repository' do - let(:project) { create(:empty_project) } - include_examples :preserve_unchanged - end - - context 'with an empty repository' do - let(:project) { create(:project_empty_repo) } - include_examples :preserve_unchanged - end - - it 'does not raise an exception on invalid URIs' do - act = link("://foo") - expect { filter(act) }.not_to raise_error - end - - context 'with a valid repository' do - it 'rebuilds relative URL for a file in the repo' do - doc = filter(link('doc/api/README.md')) - expect(doc.at_css('a')['href']). - to eq "/#{project_path}/blob/#{ref}/doc/api/README.md" - end - - it 'rebuilds relative URL for a file in the repo up one directory' do - relative_link = link('../api/README.md') - doc = filter(relative_link, requested_path: 'doc/update/7.14-to-8.0.md') - - expect(doc.at_css('a')['href']). - to eq "/#{project_path}/blob/#{ref}/doc/api/README.md" - end - - it 'rebuilds relative URL for a file in the repo up multiple directories' do - relative_link = link('../../../api/README.md') - doc = filter(relative_link, requested_path: 'doc/foo/bar/baz/README.md') - - expect(doc.at_css('a')['href']). - to eq "/#{project_path}/blob/#{ref}/doc/api/README.md" - end - - it 'rebuilds relative URL for a file in the repo with an anchor' do - doc = filter(link('README.md#section')) - expect(doc.at_css('a')['href']). - to eq "/#{project_path}/blob/#{ref}/README.md#section" - end - - it 'rebuilds relative URL for a directory in the repo' do - doc = filter(link('doc/api/')) - expect(doc.at_css('a')['href']). - to eq "/#{project_path}/tree/#{ref}/doc/api" - end - - it 'rebuilds relative URL for an image in the repo' do - doc = filter(link('files/images/logo-black.png')) - expect(doc.at_css('a')['href']). - to eq "/#{project_path}/raw/#{ref}/files/images/logo-black.png" - end - - it 'does not modify relative URL with an anchor only' do - doc = filter(link('#section-1')) - expect(doc.at_css('a')['href']).to eq '#section-1' - end - - it 'does not modify absolute URL' do - doc = filter(link('http://example.com')) - expect(doc.at_css('a')['href']).to eq 'http://example.com' - end - - it 'supports Unicode filenames' do - path = 'files/images/한글.png' - escaped = Addressable::URI.escape(path) - - # Stub these methods so the file doesn't actually need to be in the repo - allow_any_instance_of(described_class). - to receive(:file_exists?).and_return(true) - allow_any_instance_of(described_class). - to receive(:image?).with(path).and_return(true) - - doc = filter(image(escaped)) - expect(doc.at_css('img')['src']).to match '/raw/' - end - - context 'when requested path is a file in the repo' do - let(:requested_path) { 'doc/api/README.md' } - include_examples :relative_to_requested - end - - context 'when requested path is a directory in the repo' do - let(:requested_path) { 'doc/api' } - include_examples :relative_to_requested - end - end - end -end diff --git a/spec/lib/gitlab/markdown/sanitization_filter_spec.rb b/spec/lib/gitlab/markdown/sanitization_filter_spec.rb deleted file mode 100644 index 27cd00e8054..00000000000 --- a/spec/lib/gitlab/markdown/sanitization_filter_spec.rb +++ /dev/null @@ -1,199 +0,0 @@ -require 'spec_helper' - -module Gitlab::Markdown - describe SanitizationFilter do - include FilterSpecHelper - - describe 'default whitelist' do - it 'sanitizes tags that are not whitelisted' do - act = %q{<textarea>no inputs</textarea> and <blink>no blinks</blink>} - exp = 'no inputs and no blinks' - expect(filter(act).to_html).to eq exp - end - - it 'sanitizes tag attributes' do - act = %q{<a href="http://example.com/bar.html" onclick="bar">Text</a>} - exp = %q{<a href="http://example.com/bar.html">Text</a>} - expect(filter(act).to_html).to eq exp - end - - it 'sanitizes javascript in attributes' do - act = %q(<a href="javascript:alert('foo')">Text</a>) - exp = '<a>Text</a>' - expect(filter(act).to_html).to eq exp - end - - it 'allows whitelisted HTML tags from the user' do - exp = act = "<dl>\n<dt>Term</dt>\n<dd>Definition</dd>\n</dl>" - expect(filter(act).to_html).to eq exp - end - - it 'sanitizes `class` attribute on any element' do - act = %q{<strong class="foo">Strong</strong>} - expect(filter(act).to_html).to eq %q{<strong>Strong</strong>} - end - - it 'sanitizes `id` attribute on any element' do - act = %q{<em id="foo">Emphasis</em>} - expect(filter(act).to_html).to eq %q{<em>Emphasis</em>} - end - end - - describe 'custom whitelist' do - it 'customizes the whitelist only once' do - instance = described_class.new('Foo') - 3.times { instance.whitelist } - - expect(instance.whitelist[:transformers].size).to eq 5 - end - - it 'allows syntax highlighting' do - exp = act = %q{<pre class="code highlight white c"><code><span class="k">def</span></code></pre>} - expect(filter(act).to_html).to eq exp - end - - it 'sanitizes `class` attribute from non-highlight spans' do - act = %q{<span class="k">def</span>} - expect(filter(act).to_html).to eq %q{<span>def</span>} - end - - it 'allows `style` attribute on table elements' do - html = <<-HTML.strip_heredoc - <table> - <tr><th style="text-align: center">Head</th></tr> - <tr><td style="text-align: right">Body</th></tr> - </table> - HTML - - doc = filter(html) - - expect(doc.at_css('th')['style']).to eq 'text-align: center' - expect(doc.at_css('td')['style']).to eq 'text-align: right' - end - - it 'allows `span` elements' do - exp = act = %q{<span>Hello</span>} - expect(filter(act).to_html).to eq exp - end - - it 'removes `rel` attribute from `a` elements' do - act = %q{<a href="#" rel="nofollow">Link</a>} - exp = %q{<a href="#">Link</a>} - - expect(filter(act).to_html).to eq exp - end - - # Adapted from the Sanitize test suite: http://git.io/vczrM - protocols = { - 'protocol-based JS injection: simple, no spaces' => { - input: '<a href="javascript:alert(\'XSS\');">foo</a>', - output: '<a>foo</a>' - }, - - 'protocol-based JS injection: simple, spaces before' => { - input: '<a href="javascript :alert(\'XSS\');">foo</a>', - output: '<a>foo</a>' - }, - - 'protocol-based JS injection: simple, spaces after' => { - input: '<a href="javascript: alert(\'XSS\');">foo</a>', - output: '<a>foo</a>' - }, - - 'protocol-based JS injection: simple, spaces before and after' => { - input: '<a href="javascript : alert(\'XSS\');">foo</a>', - output: '<a>foo</a>' - }, - - 'protocol-based JS injection: preceding colon' => { - input: '<a href=":javascript:alert(\'XSS\');">foo</a>', - output: '<a>foo</a>' - }, - - 'protocol-based JS injection: UTF-8 encoding' => { - input: '<a href="javascript:">foo</a>', - output: '<a>foo</a>' - }, - - 'protocol-based JS injection: long UTF-8 encoding' => { - input: '<a href="javascript:">foo</a>', - output: '<a>foo</a>' - }, - - 'protocol-based JS injection: long UTF-8 encoding without semicolons' => { - input: '<a href=javascript:alert('XSS')>foo</a>', - output: '<a>foo</a>' - }, - - 'protocol-based JS injection: hex encoding' => { - input: '<a href="javascript:">foo</a>', - output: '<a>foo</a>' - }, - - 'protocol-based JS injection: long hex encoding' => { - input: '<a href="javascript:">foo</a>', - output: '<a>foo</a>' - }, - - 'protocol-based JS injection: hex encoding without semicolons' => { - input: '<a href=javascript:alert('XSS')>foo</a>', - output: '<a>foo</a>' - }, - - 'protocol-based JS injection: null char' => { - input: "<a href=java\0script:alert(\"XSS\")>foo</a>", - output: '<a href="java"></a>' - }, - - 'protocol-based JS injection: spaces and entities' => { - input: '<a href="  javascript:alert(\'XSS\');">foo</a>', - output: '<a href="">foo</a>' - }, - } - - protocols.each do |name, data| - it "handles #{name}" do - doc = filter(data[:input]) - - expect(doc.to_html).to eq data[:output] - end - end - - it 'allows non-standard anchor schemes' do - exp = %q{<a href="irc://irc.freenode.net/git">IRC</a>} - act = filter(exp) - - expect(act.to_html).to eq exp - end - - it 'allows relative links' do - exp = %q{<a href="foo/bar.md">foo/bar.md</a>} - act = filter(exp) - - expect(act.to_html).to eq exp - end - end - - context 'when pipeline is :description' do - it 'uses a stricter whitelist' do - doc = filter('<h1>Description</h1>', pipeline: :description) - expect(doc.to_html.strip).to eq 'Description' - end - - %w(pre code img ol ul li).each do |elem| - it "removes '#{elem}' elements" do - act = "<#{elem}>Description</#{elem}>" - expect(filter(act, pipeline: :description).to_html.strip). - to eq 'Description' - end - end - - %w(b i strong em a ins del sup sub p).each do |elem| - it "still allows '#{elem}' elements" do - exp = act = "<#{elem}>Description</#{elem}>" - expect(filter(act, pipeline: :description).to_html).to eq exp - end - end - end - end -end diff --git a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb deleted file mode 100644 index 9d9652dba46..00000000000 --- a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb +++ /dev/null @@ -1,118 +0,0 @@ -require 'spec_helper' - -module Gitlab::Markdown - describe SnippetReferenceFilter do - include FilterSpecHelper - - let(:project) { create(:empty_project, :public) } - let(:snippet) { create(:project_snippet, project: project) } - let(:reference) { snippet.to_reference } - - it 'requires project context' do - expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) - end - - %w(pre code a style).each do |elem| - it "ignores valid references contained inside '#{elem}' element" do - exp = act = "<#{elem}>Snippet #{reference}</#{elem}>" - expect(filter(act).to_html).to eq exp - end - end - - context 'internal reference' do - it 'links to a valid reference' do - doc = filter("See #{reference}") - - expect(doc.css('a').first.attr('href')).to eq urls. - namespace_project_snippet_url(project.namespace, project, snippet) - end - - it 'links with adjacent text' do - doc = filter("Snippet (#{reference}.)") - expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/) - end - - it 'ignores invalid snippet IDs' do - exp = act = "Snippet #{invalidate_reference(reference)}" - - expect(filter(act).to_html).to eq exp - end - - it 'includes a title attribute' do - doc = filter("Snippet #{reference}") - expect(doc.css('a').first.attr('title')).to eq "Snippet: #{snippet.title}" - end - - it 'escapes the title attribute' do - snippet.update_attribute(:title, %{"></a>whatever<a title="}) - - doc = filter("Snippet #{reference}") - expect(doc.text).to eq "Snippet #{reference}" - end - - it 'includes default classes' do - doc = filter("Snippet #{reference}") - expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet' - end - - it 'includes a data-project attribute' do - doc = filter("Snippet #{reference}") - link = doc.css('a').first - - expect(link).to have_attribute('data-project') - expect(link.attr('data-project')).to eq project.id.to_s - end - - it 'includes a data-snippet attribute' do - doc = filter("See #{reference}") - link = doc.css('a').first - - expect(link).to have_attribute('data-snippet') - expect(link.attr('data-snippet')).to eq snippet.id.to_s - end - - it 'supports an :only_path context' do - doc = filter("Snippet #{reference}", only_path: true) - link = doc.css('a').first.attr('href') - - expect(link).not_to match %r(https?://) - expect(link).to eq urls.namespace_project_snippet_url(project.namespace, project, snippet, only_path: true) - end - - it 'adds to the results hash' do - result = reference_pipeline_result("Snippet #{reference}") - expect(result[:references][:snippet]).to eq [snippet] - end - end - - context 'cross-project reference' do - let(:namespace) { create(:namespace, name: 'cross-reference') } - let(:project2) { create(:empty_project, :public, namespace: namespace) } - let(:snippet) { create(:project_snippet, project: project2) } - let(:reference) { snippet.to_reference(project) } - - it 'links to a valid reference' do - doc = filter("See #{reference}") - - expect(doc.css('a').first.attr('href')). - to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet) - end - - it 'links with adjacent text' do - doc = filter("See (#{reference}.)") - expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/) - end - - it 'ignores invalid snippet IDs on the referenced project' do - exp = act = "See #{invalidate_reference(reference)}" - - expect(filter(act).to_html).to eq exp - end - - it 'adds to the results hash' do - result = reference_pipeline_result("Snippet #{reference}") - expect(result[:references][:snippet]).to eq [snippet] - end - end - end -end diff --git a/spec/lib/gitlab/markdown/syntax_highlight_filter_spec.rb b/spec/lib/gitlab/markdown/syntax_highlight_filter_spec.rb deleted file mode 100644 index 6a490673728..00000000000 --- a/spec/lib/gitlab/markdown/syntax_highlight_filter_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -require 'spec_helper' - -module Gitlab::Markdown - describe SyntaxHighlightFilter do - include FilterSpecHelper - - it 'highlights valid code blocks' do - result = filter('<pre><code>def fun end</code>') - expect(result.to_html).to eq("<pre class=\"code highlight js-syntax-highlight plaintext\"><code>def fun end</code></pre>\n") - end - - it 'passes through invalid code blocks' do - allow_any_instance_of(SyntaxHighlightFilter).to receive(:block_code).and_raise(StandardError) - - result = filter('<pre><code>This is a test</code></pre>') - expect(result.to_html).to eq('<pre>This is a test</pre>') - end - end -end diff --git a/spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb b/spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb deleted file mode 100644 index ddf583a72c1..00000000000 --- a/spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb +++ /dev/null @@ -1,99 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' - -module Gitlab::Markdown - describe TableOfContentsFilter do - include FilterSpecHelper - - def header(level, text) - "<h#{level}>#{text}</h#{level}>\n" - end - - it 'does nothing when :no_header_anchors is truthy' do - exp = act = header(1, 'Header') - expect(filter(act, no_header_anchors: 1).to_html).to eq exp - end - - it 'does nothing with empty headers' do - exp = act = header(1, nil) - expect(filter(act).to_html).to eq exp - end - - 1.upto(6) do |i| - it "processes h#{i} elements" do - html = header(i, "Header #{i}") - doc = filter(html) - - expect(doc.css("h#{i} a").first.attr('id')).to eq "header-#{i}" - end - end - - describe 'anchor tag' do - it 'has an `anchor` class' do - doc = filter(header(1, 'Header')) - expect(doc.css('h1 a').first.attr('class')).to eq 'anchor' - end - - it 'links to the id' do - doc = filter(header(1, 'Header')) - expect(doc.css('h1 a').first.attr('href')).to eq '#header' - end - - describe 'generated IDs' do - it 'translates spaces to dashes' do - doc = filter(header(1, 'This header has spaces in it')) - expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-has-spaces-in-it' - end - - it 'squeezes multiple spaces and dashes' do - doc = filter(header(1, 'This---header is poorly-formatted')) - expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-is-poorly-formatted' - end - - it 'removes punctuation' do - doc = filter(header(1, "This, header! is, filled. with @ punctuation?")) - expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-is-filled-with-punctuation' - end - - it 'appends a unique number to duplicates' do - doc = filter(header(1, 'One') + header(2, 'One')) - - expect(doc.css('h1 a').first.attr('id')).to eq 'one' - expect(doc.css('h2 a').first.attr('id')).to eq 'one-1' - end - - it 'supports Unicode' do - doc = filter(header(1, '한글')) - expect(doc.css('h1 a').first.attr('id')).to eq '한글' - expect(doc.css('h1 a').first.attr('href')).to eq '#한글' - end - end - end - - describe 'result' do - def result(html) - HTML::Pipeline.new([described_class]).call(html) - end - - let(:results) { result(header(1, 'Header 1') + header(2, 'Header 2')) } - let(:doc) { Nokogiri::XML::DocumentFragment.parse(results[:toc]) } - - it 'is contained within a `ul` element' do - expect(doc.children.first.name).to eq 'ul' - expect(doc.children.first.attr('class')).to eq 'section-nav' - end - - it 'contains an `li` element for each header' do - expect(doc.css('li').length).to eq 2 - - links = doc.css('li a') - - expect(links.first.attr('href')).to eq '#header-1' - expect(links.first.text).to eq 'Header 1' - expect(links.last.attr('href')).to eq '#header-2' - expect(links.last.text).to eq 'Header 2' - end - end - end -end diff --git a/spec/lib/gitlab/markdown/task_list_filter_spec.rb b/spec/lib/gitlab/markdown/task_list_filter_spec.rb deleted file mode 100644 index 94f39cc966e..00000000000 --- a/spec/lib/gitlab/markdown/task_list_filter_spec.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'spec_helper' - -module Gitlab::Markdown - describe TaskListFilter do - include FilterSpecHelper - - it 'does not apply `task-list` class to non-task lists' do - exp = act = %(<ul><li>Item</li></ul>) - expect(filter(act).to_html).to eq exp - end - end -end diff --git a/spec/lib/gitlab/markdown/upload_link_filter_spec.rb b/spec/lib/gitlab/markdown/upload_link_filter_spec.rb deleted file mode 100644 index 9ae45a6f559..00000000000 --- a/spec/lib/gitlab/markdown/upload_link_filter_spec.rb +++ /dev/null @@ -1,75 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' - -module Gitlab::Markdown - describe UploadLinkFilter do - def filter(doc, contexts = {}) - contexts.reverse_merge!({ - project: project - }) - - described_class.call(doc, contexts) - end - - def image(path) - %(<img src="#{path}" />) - end - - def link(path) - %(<a href="#{path}">#{path}</a>) - end - - let(:project) { create(:project) } - - shared_examples :preserve_unchanged do - it 'does not modify any relative URL in anchor' do - doc = filter(link('README.md')) - expect(doc.at_css('a')['href']).to eq 'README.md' - end - - it 'does not modify any relative URL in image' do - doc = filter(image('files/images/logo-black.png')) - expect(doc.at_css('img')['src']).to eq 'files/images/logo-black.png' - end - end - - it 'does not raise an exception on invalid URIs' do - act = link("://foo") - expect { filter(act) }.not_to raise_error - end - - context 'with a valid repository' do - it 'rebuilds relative URL for a link' do - doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')) - expect(doc.at_css('a')['href']). - to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg" - end - - it 'rebuilds relative URL for an image' do - doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')) - expect(doc.at_css('a')['href']). - to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg" - end - - it 'does not modify absolute URL' do - doc = filter(link('http://example.com')) - expect(doc.at_css('a')['href']).to eq 'http://example.com' - end - - it 'supports Unicode filenames' do - path = '/uploads/한글.png' - escaped = Addressable::URI.escape(path) - - # Stub these methods so the file doesn't actually need to be in the repo - allow_any_instance_of(described_class). - to receive(:file_exists?).and_return(true) - allow_any_instance_of(described_class). - to receive(:image?).with(path).and_return(true) - - doc = filter(image(escaped)) - expect(doc.at_css('img')['src']).to match "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/%ED%95%9C%EA%B8%80.png" - end - end - end -end diff --git a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb deleted file mode 100644 index d9e0d7c42db..00000000000 --- a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb +++ /dev/null @@ -1,122 +0,0 @@ -require 'spec_helper' - -module Gitlab::Markdown - describe UserReferenceFilter do - include FilterSpecHelper - - let(:project) { create(:empty_project, :public) } - let(:user) { create(:user) } - let(:reference) { user.to_reference } - - it 'requires project context' do - expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) - end - - it 'ignores invalid users' do - exp = act = "Hey #{invalidate_reference(reference)}" - expect(filter(act).to_html).to eq(exp) - end - - %w(pre code a style).each do |elem| - it "ignores valid references contained inside '#{elem}' element" do - exp = act = "<#{elem}>Hey #{reference}</#{elem}>" - expect(filter(act).to_html).to eq exp - end - end - - context 'mentioning @all' do - let(:reference) { User.reference_prefix + 'all' } - - before do - project.team << [project.creator, :developer] - end - - it 'supports a special @all mention' do - doc = filter("Hey #{reference}") - expect(doc.css('a').length).to eq 1 - expect(doc.css('a').first.attr('href')) - .to eq urls.namespace_project_url(project.namespace, project) - end - - it 'adds to the results hash' do - result = reference_pipeline_result("Hey #{reference}") - expect(result[:references][:user]).to eq [project.creator] - end - end - - context 'mentioning a user' do - it 'links to a User' do - doc = filter("Hey #{reference}") - expect(doc.css('a').first.attr('href')).to eq urls.user_url(user) - end - - it 'links to a User with a period' do - user = create(:user, name: 'alphA.Beta') - - doc = filter("Hey #{user.to_reference}") - expect(doc.css('a').length).to eq 1 - end - - it 'links to a User with an underscore' do - user = create(:user, name: 'ping_pong_king') - - doc = filter("Hey #{user.to_reference}") - expect(doc.css('a').length).to eq 1 - end - - it 'includes a data-user attribute' do - doc = filter("Hey #{reference}") - link = doc.css('a').first - - expect(link).to have_attribute('data-user') - expect(link.attr('data-user')).to eq user.namespace.owner_id.to_s - end - - it 'adds to the results hash' do - result = reference_pipeline_result("Hey #{reference}") - expect(result[:references][:user]).to eq [user] - end - end - - context 'mentioning a group' do - let(:group) { create(:group) } - let(:reference) { group.to_reference } - - it 'links to the Group' do - doc = filter("Hey #{reference}") - expect(doc.css('a').first.attr('href')).to eq urls.group_url(group) - end - - it 'includes a data-group attribute' do - doc = filter("Hey #{reference}") - link = doc.css('a').first - - expect(link).to have_attribute('data-group') - expect(link.attr('data-group')).to eq group.id.to_s - end - - it 'adds to the results hash' do - result = reference_pipeline_result("Hey #{reference}") - expect(result[:references][:user]).to eq group.users - end - end - - it 'links with adjacent text' do - doc = filter("Mention me (#{reference}.)") - expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/) - end - - it 'includes default classes' do - doc = filter("Hey #{reference}") - expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member' - end - - it 'supports an :only_path context' do - doc = filter("Hey #{reference}", only_path: true) - link = doc.css('a').first.attr('href') - - expect(link).not_to match %r(https?://) - expect(link).to eq urls.user_path(user) - end - end -end diff --git a/spec/lib/gitlab/markup_helper_spec.rb b/spec/lib/gitlab/markup_helper_spec.rb index e610fab05da..93b91b849f2 100644 --- a/spec/lib/gitlab/markup_helper_spec.rb +++ b/spec/lib/gitlab/markup_helper_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::MarkupHelper do +describe Gitlab::MarkupHelper, lib: true do describe '#markup?' do %w(textile rdoc org creole wiki mediawiki rst adoc ad asciidoc mdown md markdown).each do |type| diff --git a/spec/lib/gitlab/note_data_builder_spec.rb b/spec/lib/gitlab/note_data_builder_spec.rb index 448cd0c6880..6cbdae737f4 100644 --- a/spec/lib/gitlab/note_data_builder_spec.rb +++ b/spec/lib/gitlab/note_data_builder_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Gitlab::NoteDataBuilder' do +describe 'Gitlab::NoteDataBuilder', lib: true do let(:project) { create(:project) } let(:user) { create(:user) } let(:data) { Gitlab::NoteDataBuilder.build(note, user) } diff --git a/spec/lib/gitlab/o_auth/auth_hash_spec.rb b/spec/lib/gitlab/o_auth/auth_hash_spec.rb index 5632f2306ec..a4f8b44e38e 100644 --- a/spec/lib/gitlab/o_auth/auth_hash_spec.rb +++ b/spec/lib/gitlab/o_auth/auth_hash_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::OAuth::AuthHash do +describe Gitlab::OAuth::AuthHash, lib: true do let(:auth_hash) do Gitlab::OAuth::AuthHash.new( OmniAuth::AuthHash.new( diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb index fd3ab1fb7c8..925bc442a90 100644 --- a/spec/lib/gitlab/o_auth/user_spec.rb +++ b/spec/lib/gitlab/o_auth/user_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::OAuth::User do +describe Gitlab::OAuth::User, lib: true do let(:oauth_user) { Gitlab::OAuth::User.new(auth_hash) } let(:gl_user) { oauth_user.gl_user } let(:uid) { 'my-uid' } diff --git a/spec/lib/gitlab/popen_spec.rb b/spec/lib/gitlab/popen_spec.rb index e53efec6c67..795cf241278 100644 --- a/spec/lib/gitlab/popen_spec.rb +++ b/spec/lib/gitlab/popen_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Gitlab::Popen', no_db: true do +describe 'Gitlab::Popen', lib: true, no_db: true do let(:path) { Rails.root.join('tmp').to_s } before do diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb index 19327ac8ce0..efc2e5f4ef1 100644 --- a/spec/lib/gitlab/project_search_results_spec.rb +++ b/spec/lib/gitlab/project_search_results_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::ProjectSearchResults do +describe Gitlab::ProjectSearchResults, lib: true do let(:project) { create(:project) } let(:query) { 'hello world' } diff --git a/spec/lib/gitlab/push_data_builder_spec.rb b/spec/lib/gitlab/push_data_builder_spec.rb index 02710742625..3ef61685398 100644 --- a/spec/lib/gitlab/push_data_builder_spec.rb +++ b/spec/lib/gitlab/push_data_builder_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Gitlab::PushDataBuilder' do +describe 'Gitlab::PushDataBuilder', lib: true do let(:project) { create(:project) } let(:user) { create(:user) } @@ -17,9 +17,9 @@ describe 'Gitlab::PushDataBuilder' do it { expect(data[:repository][:git_ssh_url]).to eq(project.ssh_url_to_repo) } it { expect(data[:repository][:visibility_level]).to eq(project.visibility_level) } it { expect(data[:total_commits_count]).to eq(3) } - it { expect(data[:added]).to eq(["gitlab-grack"]) } - it { expect(data[:modified]).to eq([".gitmodules", "files/ruby/popen.rb", "files/ruby/regex.rb"]) } - it { expect(data[:removed]).to eq([]) } + it { expect(data[:commits].first[:added]).to eq(["gitlab-grack"]) } + it { expect(data[:commits].first[:modified]).to eq([".gitmodules"]) } + it { expect(data[:commits].first[:removed]).to eq([]) } end describe :build do @@ -38,8 +38,5 @@ describe 'Gitlab::PushDataBuilder' do it { expect(data[:ref]).to eq('refs/tags/v1.1.0') } it { expect(data[:commits]).to be_empty } it { expect(data[:total_commits_count]).to be_zero } - it { expect(data[:added]).to eq([]) } - it { expect(data[:modified]).to eq([]) } - it { expect(data[:removed]).to eq([]) } end end diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb index ad84d2274e8..66dc5d4911d 100644 --- a/spec/lib/gitlab/reference_extractor_spec.rb +++ b/spec/lib/gitlab/reference_extractor_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::ReferenceExtractor do +describe Gitlab::ReferenceExtractor, lib: true do let(:project) { create(:project) } subject { Gitlab::ReferenceExtractor.new(project, project.creator) } diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb index 7fdc8fa600d..d67ee423b9b 100644 --- a/spec/lib/gitlab/regex_spec.rb +++ b/spec/lib/gitlab/regex_spec.rb @@ -1,7 +1,7 @@ # coding: utf-8 require 'spec_helper' -describe Gitlab::Regex do +describe Gitlab::Regex, lib: true do describe 'project path regex' do it { expect('gitlab-ce').to match(Gitlab::Regex.project_path_regex) } it { expect('gitlab_git').to match(Gitlab::Regex.project_path_regex) } diff --git a/spec/lib/gitlab/sherlock/collection_spec.rb b/spec/lib/gitlab/sherlock/collection_spec.rb index a8a9d6fc7bc..de6bb86c5dd 100644 --- a/spec/lib/gitlab/sherlock/collection_spec.rb +++ b/spec/lib/gitlab/sherlock/collection_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Sherlock::Collection do +describe Gitlab::Sherlock::Collection, lib: true do let(:collection) { described_class.new } let(:transaction) do diff --git a/spec/lib/gitlab/sherlock/file_sample_spec.rb b/spec/lib/gitlab/sherlock/file_sample_spec.rb index f05a59f56f6..cadf8bbce78 100644 --- a/spec/lib/gitlab/sherlock/file_sample_spec.rb +++ b/spec/lib/gitlab/sherlock/file_sample_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Sherlock::FileSample do +describe Gitlab::Sherlock::FileSample, lib: true do let(:sample) { described_class.new(__FILE__, [], 150.4, 2) } describe '#id' do diff --git a/spec/lib/gitlab/sherlock/line_profiler_spec.rb b/spec/lib/gitlab/sherlock/line_profiler_spec.rb index 8f2e1299714..d57627bba2b 100644 --- a/spec/lib/gitlab/sherlock/line_profiler_spec.rb +++ b/spec/lib/gitlab/sherlock/line_profiler_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Sherlock::LineProfiler do +describe Gitlab::Sherlock::LineProfiler, lib: true do let(:profiler) { described_class.new } describe '#profile' do diff --git a/spec/lib/gitlab/sherlock/line_sample_spec.rb b/spec/lib/gitlab/sherlock/line_sample_spec.rb index 5f02f6a3213..f9b61f8684e 100644 --- a/spec/lib/gitlab/sherlock/line_sample_spec.rb +++ b/spec/lib/gitlab/sherlock/line_sample_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Sherlock::LineSample do +describe Gitlab::Sherlock::LineSample, lib: true do let(:sample) { described_class.new(150.0, 4) } describe '#duration' do diff --git a/spec/lib/gitlab/sherlock/location_spec.rb b/spec/lib/gitlab/sherlock/location_spec.rb index b295a624b35..5739afa6b1e 100644 --- a/spec/lib/gitlab/sherlock/location_spec.rb +++ b/spec/lib/gitlab/sherlock/location_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Sherlock::Location do +describe Gitlab::Sherlock::Location, lib: true do let(:location) { described_class.new(__FILE__, 1) } describe 'from_ruby_location' do diff --git a/spec/lib/gitlab/sherlock/middleware_spec.rb b/spec/lib/gitlab/sherlock/middleware_spec.rb index aa74fc53a79..2bbeb25ce98 100644 --- a/spec/lib/gitlab/sherlock/middleware_spec.rb +++ b/spec/lib/gitlab/sherlock/middleware_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Sherlock::Middleware do +describe Gitlab::Sherlock::Middleware, lib: true do let(:app) { double(:app) } let(:middleware) { described_class.new(app) } diff --git a/spec/lib/gitlab/sherlock/query_spec.rb b/spec/lib/gitlab/sherlock/query_spec.rb index a9afef5dc1d..05da915ccfd 100644 --- a/spec/lib/gitlab/sherlock/query_spec.rb +++ b/spec/lib/gitlab/sherlock/query_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Sherlock::Query do +describe Gitlab::Sherlock::Query, lib: true do let(:started_at) { Time.utc(2015, 1, 1) } let(:finished_at) { started_at + 5 } diff --git a/spec/lib/gitlab/sherlock/transaction_spec.rb b/spec/lib/gitlab/sherlock/transaction_spec.rb index bb49fb65cf8..7553f2a045f 100644 --- a/spec/lib/gitlab/sherlock/transaction_spec.rb +++ b/spec/lib/gitlab/sherlock/transaction_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Sherlock::Transaction do +describe Gitlab::Sherlock::Transaction, lib: true do let(:transaction) { described_class.new('POST', '/cat_pictures') } describe '#id' do @@ -84,6 +84,19 @@ describe Gitlab::Sherlock::Transaction do end end + describe '#query_duration' do + it 'returns the total query duration in seconds' do + time = Time.now + query1 = Gitlab::Sherlock::Query.new('SELECT 1', time, time + 5) + query2 = Gitlab::Sherlock::Query.new('SELECT 2', time, time + 2) + + transaction.queries << query1 + transaction.queries << query2 + + expect(transaction.query_duration).to be_within(0.1).of(7.0) + end + end + describe '#to_param' do it 'returns the transaction ID' do expect(transaction.to_param).to eq(transaction.id) diff --git a/spec/lib/gitlab/sql/union_spec.rb b/spec/lib/gitlab/sql/union_spec.rb new file mode 100644 index 00000000000..0cdbab87544 --- /dev/null +++ b/spec/lib/gitlab/sql/union_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' + +describe Gitlab::SQL::Union, lib: true do + describe '#to_sql' do + it 'returns a String joining relations together using a UNION' do + rel1 = User.where(email: 'alice@example.com') + rel2 = User.where(email: 'bob@example.com') + union = described_class.new([rel1, rel2]) + + sql1 = rel1.reorder(nil).to_sql + sql2 = rel2.reorder(nil).to_sql + + expect(union.to_sql).to eq("#{sql1}\nUNION\n#{sql2}") + end + end +end diff --git a/spec/lib/gitlab/themes_spec.rb b/spec/lib/gitlab/themes_spec.rb index e554458e41c..7a140518dd2 100644 --- a/spec/lib/gitlab/themes_spec.rb +++ b/spec/lib/gitlab/themes_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Themes do +describe Gitlab::Themes, lib: true do describe '.body_classes' do it 'returns a space-separated list of class names' do css = described_class.body_classes diff --git a/spec/lib/gitlab/upgrader_spec.rb b/spec/lib/gitlab/upgrader_spec.rb index 8df84665e16..e958e087a80 100644 --- a/spec/lib/gitlab/upgrader_spec.rb +++ b/spec/lib/gitlab/upgrader_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Upgrader do +describe Gitlab::Upgrader, lib: true do let(:upgrader) { Gitlab::Upgrader.new } let(:current_version) { Gitlab::VERSION } diff --git a/spec/lib/gitlab/uploads_transfer_spec.rb b/spec/lib/gitlab/uploads_transfer_spec.rb index 260364a513e..4092f7fb638 100644 --- a/spec/lib/gitlab/uploads_transfer_spec.rb +++ b/spec/lib/gitlab/uploads_transfer_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::UploadsTransfer do +describe Gitlab::UploadsTransfer, lib: true do before do @root_dir = File.join(Rails.root, "public", "uploads") @upload_transfer = Gitlab::UploadsTransfer.new diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb index 5153ed15af3..f023be6ae45 100644 --- a/spec/lib/gitlab/url_builder_spec.rb +++ b/spec/lib/gitlab/url_builder_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::UrlBuilder do +describe Gitlab::UrlBuilder, lib: true do describe 'When asking for an issue' do it 'returns the issue url' do issue = create(:issue) diff --git a/spec/lib/gitlab/version_info_spec.rb b/spec/lib/gitlab/version_info_spec.rb index 18f71b40fe0..706ee9bec58 100644 --- a/spec/lib/gitlab/version_info_spec.rb +++ b/spec/lib/gitlab/version_info_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Gitlab::VersionInfo', no_db: true do +describe 'Gitlab::VersionInfo', lib: true, no_db: true do before do @unknown = Gitlab::VersionInfo.new @v0_0_1 = Gitlab::VersionInfo.new(0, 0, 1) diff --git a/spec/lib/repository_cache_spec.rb b/spec/lib/repository_cache_spec.rb index 37240d51310..63b5292b098 100644 --- a/spec/lib/repository_cache_spec.rb +++ b/spec/lib/repository_cache_spec.rb @@ -1,6 +1,6 @@ require_relative '../../lib/repository_cache' -describe RepositoryCache do +describe RepositoryCache, lib: true do let(:backend) { double('backend').as_null_object } let(:cache) { RepositoryCache.new('example', backend) } diff --git a/spec/lib/votes_spec.rb b/spec/lib/votes_spec.rb deleted file mode 100644 index 39e5d054e62..00000000000 --- a/spec/lib/votes_spec.rb +++ /dev/null @@ -1,188 +0,0 @@ -require 'spec_helper' - -describe Issue, 'Votes' do - let(:issue) { create(:issue) } - - describe "#upvotes" do - it "with no notes has a 0/0 score" do - expect(issue.upvotes).to eq(0) - end - - it "should recognize non-+1 notes" do - add_note "No +1 here" - expect(issue.notes.size).to eq(1) - expect(issue.notes.first.upvote?).to be_falsey - expect(issue.upvotes).to eq(0) - end - - it "should recognize a single +1 note" do - add_note "+1 This is awesome" - expect(issue.upvotes).to eq(1) - end - - it 'should recognize multiple +1 notes' do - add_note '+1 This is awesome', create(:user) - add_note '+1 I want this', create(:user) - expect(issue.upvotes).to eq(2) - end - - it 'should not count 2 +1 votes from the same user' do - add_note '+1 This is awesome' - add_note '+1 I want this' - expect(issue.upvotes).to eq(1) - end - end - - describe "#downvotes" do - it "with no notes has a 0/0 score" do - expect(issue.downvotes).to eq(0) - end - - it "should recognize non--1 notes" do - add_note "Almost got a -1" - expect(issue.notes.size).to eq(1) - expect(issue.notes.first.downvote?).to be_falsey - expect(issue.downvotes).to eq(0) - end - - it "should recognize a single -1 note" do - add_note "-1 This is bad" - expect(issue.downvotes).to eq(1) - end - - it "should recognize multiple -1 notes" do - add_note('-1 This is bad', create(:user)) - add_note('-1 Away with this', create(:user)) - expect(issue.downvotes).to eq(2) - end - end - - describe "#votes_count" do - it "with no notes has a 0/0 score" do - expect(issue.votes_count).to eq(0) - end - - it "should recognize non notes" do - add_note "No +1 here" - expect(issue.notes.size).to eq(1) - expect(issue.votes_count).to eq(0) - end - - it "should recognize a single +1 note" do - add_note "+1 This is awesome" - expect(issue.votes_count).to eq(1) - end - - it "should recognize a single -1 note" do - add_note "-1 This is bad" - expect(issue.votes_count).to eq(1) - end - - it "should recognize multiple notes" do - add_note('+1 This is awesome', create(:user)) - add_note('-1 This is bad', create(:user)) - add_note('+1 I want this', create(:user)) - expect(issue.votes_count).to eq(3) - end - - it 'should not count 2 -1 votes from the same user' do - add_note '-1 This is suspicious' - add_note '-1 This is bad' - expect(issue.votes_count).to eq(1) - end - end - - describe "#upvotes_in_percent" do - it "with no notes has a 0% score" do - expect(issue.upvotes_in_percent).to eq(0) - end - - it "should count a single 1 note as 100%" do - add_note "+1 This is awesome" - expect(issue.upvotes_in_percent).to eq(100) - end - - it 'should count multiple +1 notes as 100%' do - add_note('+1 This is awesome', create(:user)) - add_note('+1 I want this', create(:user)) - expect(issue.upvotes_in_percent).to eq(100) - end - - it 'should count fractions for multiple +1 and -1 notes correctly' do - add_note('+1 This is awesome', create(:user)) - add_note('+1 I want this', create(:user)) - add_note('-1 This is bad', create(:user)) - add_note('+1 me too', create(:user)) - expect(issue.upvotes_in_percent).to eq(75) - end - end - - describe "#downvotes_in_percent" do - it "with no notes has a 0% score" do - expect(issue.downvotes_in_percent).to eq(0) - end - - it "should count a single -1 note as 100%" do - add_note "-1 This is bad" - expect(issue.downvotes_in_percent).to eq(100) - end - - it 'should count multiple -1 notes as 100%' do - add_note('-1 This is bad', create(:user)) - add_note('-1 Away with this', create(:user)) - expect(issue.downvotes_in_percent).to eq(100) - end - - it 'should count fractions for multiple +1 and -1 notes correctly' do - add_note('+1 This is awesome', create(:user)) - add_note('+1 I want this', create(:user)) - add_note('-1 This is bad', create(:user)) - add_note('+1 me too', create(:user)) - expect(issue.downvotes_in_percent).to eq(25) - end - end - - describe '#filter_superceded_votes' do - - it 'should count a users vote only once amongst multiple votes' do - add_note('-1 This needs work before I will accept it') - add_note('+1 I want this', create(:user)) - add_note('+1 This is is awesome', create(:user)) - add_note('+1 this looks good now') - add_note('+1 This is awesome', create(:user)) - add_note('+1 me too', create(:user)) - expect(issue.downvotes).to eq(0) - expect(issue.upvotes).to eq(5) - end - - it 'should count each users vote only once' do - add_note '-1 This needs work before it will be accepted' - add_note '+1 I like this' - add_note '+1 I still like this' - add_note '+1 I really like this' - add_note '+1 Give me this now!!!!' - expect(issue.downvotes).to eq(0) - expect(issue.upvotes).to eq(1) - end - - it 'should count a users vote only once without caring about comments' do - add_note '-1 This needs work before it will be accepted' - add_note 'Comment 1' - add_note 'Another comment' - add_note '+1 vote' - add_note 'final comment' - expect(issue.downvotes).to eq(0) - expect(issue.upvotes).to eq(1) - end - - end - - def add_note(text, author = issue.author) - created_at = Time.now - 1.hour + Note.count.seconds - issue.notes << create(:note, - note: text, - project: issue.project, - author_id: author.id, - created_at: created_at) - end -end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 47863d54579..27e509933b2 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -77,6 +77,32 @@ describe Notify do end end + shared_examples 'it should have Gmail Actions links' do + it { is_expected.to have_body_text /ViewAction/ } + end + + shared_examples 'it should not have Gmail Actions links' do + it { is_expected.to_not have_body_text /ViewAction/ } + end + + shared_examples 'it should show Gmail Actions View Issue link' do + it_behaves_like 'it should have Gmail Actions links' + + it { is_expected.to have_body_text /View Issue/ } + end + + shared_examples 'it should show Gmail Actions View Merge request link' do + it_behaves_like 'it should have Gmail Actions links' + + it { is_expected.to have_body_text /View Merge request/ } + end + + shared_examples 'it should show Gmail Actions View Commit link' do + it_behaves_like 'it should have Gmail Actions links' + + it { is_expected.to have_body_text /View Commit/ } + end + describe 'for new users, the email' do let(:example_site_path) { root_path } let(:new_user) { create(:user, email: new_user_address, created_by_id: 1) } @@ -87,6 +113,7 @@ describe Notify do it_behaves_like 'an email sent from GitLab' it_behaves_like 'a new user email', new_user_address + it_behaves_like 'it should not have Gmail Actions links' it 'contains the password text' do is_expected.to have_body_text /Click here to set your password/ @@ -115,6 +142,7 @@ describe Notify do it_behaves_like 'an email sent from GitLab' it_behaves_like 'a new user email', new_user_address + it_behaves_like 'it should not have Gmail Actions links' it 'should not contain the new user\'s password' do is_expected.not_to have_body_text /password/ @@ -127,6 +155,7 @@ describe Notify do subject { Notify.new_ssh_key_email(key.id) } it_behaves_like 'an email sent from GitLab' + it_behaves_like 'it should not have Gmail Actions links' it 'is sent to the new user' do is_expected.to deliver_to key.user.email @@ -150,6 +179,8 @@ describe Notify do subject { Notify.new_email_email(email.id) } + it_behaves_like 'it should not have Gmail Actions links' + it 'is sent to the new user' do is_expected.to deliver_to email.user.email end @@ -194,6 +225,7 @@ describe Notify do it_behaves_like 'an assignee email' it_behaves_like 'an email starting a new thread', 'issue' + it_behaves_like 'it should show Gmail Actions View Issue link' it 'has the correct subject' do is_expected.to have_subject /#{project.name} \| #{issue.title} \(##{issue.iid}\)/ @@ -207,16 +239,19 @@ describe Notify do describe 'that are new with a description' do subject { Notify.new_issue_email(issue_with_description.assignee_id, issue_with_description.id) } + it_behaves_like 'it should show Gmail Actions View Issue link' + it 'contains the description' do is_expected.to have_body_text /#{issue_with_description.description}/ end end describe 'that have been reassigned' do - subject { Notify.reassigned_issue_email(recipient.id, issue.id, previous_assignee.id, current_user) } + subject { Notify.reassigned_issue_email(recipient.id, issue.id, previous_assignee.id, 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 'is sent as the author' do sender = subject.header[:from].addrs[0] @@ -243,9 +278,10 @@ describe Notify do describe 'status changed' do let(:status) { 'closed' } - subject { Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user) } + subject { Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user.id) } it_behaves_like 'an answer to an existing thread', 'issue' + it_behaves_like 'it should show Gmail Actions View Issue link' it 'is sent as the author' do sender = subject.header[:from].addrs[0] @@ -269,7 +305,6 @@ describe Notify do is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/ end end - end context 'for merge requests' do @@ -282,6 +317,7 @@ describe Notify do it_behaves_like 'an assignee email' it_behaves_like 'an email starting a new thread', 'merge_request' + it_behaves_like 'it should show Gmail Actions View Merge request link' it 'has the correct subject' do is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/ @@ -307,6 +343,8 @@ describe Notify do describe 'that are new with a description' do subject { Notify.new_merge_request_email(merge_request_with_description.assignee_id, merge_request_with_description.id) } + it_behaves_like 'it should show Gmail Actions View Merge request link' + it 'contains the description' do is_expected.to have_body_text /#{merge_request_with_description.description}/ end @@ -317,6 +355,7 @@ describe Notify do 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 'is sent as the author' do sender = subject.header[:from].addrs[0] @@ -343,9 +382,10 @@ describe Notify do describe 'status changed' do let(:status) { 'reopened' } - subject { Notify.merge_request_status_email(recipient.id, merge_request.id, status, current_user) } + subject { Notify.merge_request_status_email(recipient.id, merge_request.id, status, current_user.id) } it_behaves_like 'an answer to an existing thread', 'merge_request' + it_behaves_like 'it should show Gmail Actions View Merge request link' it 'is sent as the author' do sender = subject.header[:from].addrs[0] @@ -375,6 +415,7 @@ describe Notify do 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 'is sent as the merge author' do sender = subject.header[:from].addrs[0] @@ -403,6 +444,7 @@ describe Notify do subject { Notify.project_was_moved_email(project.id, user.id, "gitlab/gitlab") } it_behaves_like 'an email sent from GitLab' + it_behaves_like 'it should not have Gmail Actions links' it 'has the correct subject' do is_expected.to have_subject /Project was moved/ @@ -424,13 +466,16 @@ describe Notify do subject { Notify.project_access_granted_email(project_member.id) } it_behaves_like 'an email sent from GitLab' + it_behaves_like 'it should not have Gmail Actions links' it 'has the correct subject' do is_expected.to have_subject /Access to project was granted/ end + it 'contains name of project' do is_expected.to have_body_text /#{project.name}/ end + it 'contains new user role' do is_expected.to have_body_text /#{project_member.human_access}/ end @@ -445,6 +490,8 @@ describe Notify do end shared_examples 'a note email' do + it_behaves_like 'it should have Gmail Actions links' + it 'is sent as the author' do sender = subject.header[:from].addrs[0] expect(sender.display_name).to eq(note_author.name) @@ -469,6 +516,7 @@ describe Notify do it_behaves_like 'a note email' it_behaves_like 'an answer to an existing thread', 'commit' + it_behaves_like 'it should show Gmail Actions View Commit link' it 'has the correct subject' do is_expected.to have_subject /#{commit.title} \(#{commit.short_id}\)/ @@ -488,6 +536,7 @@ describe Notify do it_behaves_like 'a note 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 'has the correct subject' do is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/ @@ -507,6 +556,7 @@ describe Notify do it_behaves_like 'a note email' it_behaves_like 'an answer to an existing thread', 'issue' + it_behaves_like 'it should show Gmail Actions View Issue link' it 'has the correct subject' do is_expected.to have_subject /#{issue.title} \(##{issue.iid}\)/ @@ -527,6 +577,7 @@ describe Notify do subject { Notify.group_access_granted_email(membership.id) } it_behaves_like 'an email sent from GitLab' + it_behaves_like 'it should not have Gmail Actions links' it 'has the correct subject' do is_expected.to have_subject /Access to group was granted/ @@ -546,8 +597,10 @@ describe Notify do let(:user) { create(:user, email: 'old-email@mail.com') } before do - user.email = "new-email@mail.com" - user.save + perform_enqueued_jobs do + user.email = "new-email@mail.com" + user.save + end end subject { ActionMailer::Base.deliveries.last } @@ -574,6 +627,8 @@ describe Notify do subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :create) } + it_behaves_like 'it should not have Gmail Actions links' + it 'is sent as the author' do sender = subject.header[:from].addrs[0] expect(sender.display_name).to eq(user.name) @@ -600,6 +655,8 @@ describe Notify do subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/tags/v1.0', action: :create) } + it_behaves_like 'it should not have Gmail Actions links' + it 'is sent as the author' do sender = subject.header[:from].addrs[0] expect(sender.display_name).to eq(user.name) @@ -625,6 +682,8 @@ describe Notify do subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :delete) } + it_behaves_like 'it should not have Gmail Actions links' + it 'is sent as the author' do sender = subject.header[:from].addrs[0] expect(sender.display_name).to eq(user.name) @@ -646,6 +705,8 @@ describe Notify do subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/tags/v1.0', action: :delete) } + it_behaves_like 'it should not have Gmail Actions links' + it 'is sent as the author' do sender = subject.header[:from].addrs[0] expect(sender.display_name).to eq(user.name) @@ -671,6 +732,8 @@ describe Notify do subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, reverse_compare: false, send_from_committer_email: send_from_committer_email) } + it_behaves_like 'it should not have Gmail Actions links' + it 'is sent as the author' do sender = subject.header[:from].addrs[0] expect(sender.display_name).to eq(user.name) @@ -774,6 +837,8 @@ describe Notify do subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare) } + it_behaves_like 'it should show Gmail Actions View Commit link' + it 'is sent as the author' do sender = subject.header[:from].addrs[0] expect(sender.display_name).to eq(user.name) diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index f01fe8bd398..b67b84959d9 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -23,6 +23,10 @@ # after_sign_out_path :string(255) # session_expire_delay :integer default(10080), not null # import_sources :text +# help_page_text :text +# admin_notification_email :string(255) +# shared_runners_enabled :boolean default(TRUE), not null +# max_artifacts_size :integer default(100), not null # require 'spec_helper' @@ -32,6 +36,22 @@ describe ApplicationSetting, models: true do it { expect(setting).to be_valid } + describe 'validations' do + let(:http) { 'http://example.com' } + let(:https) { 'https://example.com' } + let(:ftp) { 'ftp://example.com' } + + it { is_expected.to allow_value(nil).for(:home_page_url) } + it { is_expected.to allow_value(http).for(:home_page_url) } + it { is_expected.to allow_value(https).for(:home_page_url) } + it { is_expected.not_to allow_value(ftp).for(:home_page_url) } + + it { is_expected.to allow_value(nil).for(:after_sign_out_path) } + it { is_expected.to allow_value(http).for(:after_sign_out_path) } + it { is_expected.to allow_value(https).for(:after_sign_out_path) } + it { is_expected.not_to allow_value(ftp).for(:after_sign_out_path) } + end + context 'restricted signup domains' do it 'set single domain' do setting.restricted_signup_domains_raw = 'example.com' diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb index d80748f23a4..e4cac105110 100644 --- a/spec/models/broadcast_message_spec.rb +++ b/spec/models/broadcast_message_spec.rb @@ -15,11 +15,26 @@ require 'spec_helper' -describe BroadcastMessage do +describe BroadcastMessage, models: true do subject { create(:broadcast_message) } it { is_expected.to be_valid } + describe 'validations' do + let(:triplet) { '#000' } + let(:hex) { '#AABBCC' } + + it { is_expected.to allow_value(nil).for(:color) } + it { is_expected.to allow_value(triplet).for(:color) } + it { is_expected.to allow_value(hex).for(:color) } + it { is_expected.not_to allow_value('000').for(:color) } + + it { is_expected.to allow_value(nil).for(:font) } + it { is_expected.to allow_value(triplet).for(:font) } + it { is_expected.to allow_value(hex).for(:font) } + it { is_expected.not_to allow_value('000').for(:font) } + end + describe :current do it "should return last message if time match" do broadcast_message = create(:broadcast_message, starts_at: Time.now.yesterday, ends_at: Time.now.tomorrow) diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index 7f5abb83ac2..70c831b7cbe 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -25,7 +25,7 @@ require 'spec_helper' -describe Ci::Build do +describe Ci::Build, models: true do let(:project) { FactoryGirl.create :ci_project } let(:gl_project) { FactoryGirl.create :empty_project, gitlab_ci_project: project } let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project } @@ -400,4 +400,19 @@ describe Ci::Build do end end end + + describe :download_url do + subject { build.download_url } + + it "should be nil if artifact doesn't exist" do + build.update_attributes(artifacts_file: nil) + is_expected.to be_nil + end + + it 'should be nil if artifact exist' do + gif = fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') + build.update_attributes(artifacts_file: gif) + is_expected.to_not be_nil + end + end end diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb index 44dbd083f06..89813cdf7fc 100644 --- a/spec/models/ci/commit_spec.rb +++ b/spec/models/ci/commit_spec.rb @@ -1,23 +1,24 @@ # == Schema Information # -# Table name: commits +# Table name: ci_commits # -# id :integer not null, primary key -# project_id :integer -# ref :string(255) -# sha :string(255) -# before_sha :string(255) -# push_data :text -# created_at :datetime -# updated_at :datetime -# tag :boolean default(FALSE) -# yaml_errors :text -# committed_at :datetime +# id :integer not null, primary key +# project_id :integer +# ref :string(255) +# sha :string(255) +# before_sha :string(255) +# push_data :text +# created_at :datetime +# updated_at :datetime +# tag :boolean default(FALSE) +# yaml_errors :text +# committed_at :datetime +# gl_project_id :integer # require 'spec_helper' -describe Ci::Commit do +describe Ci::Commit, models: true do let(:project) { FactoryGirl.create :ci_project } let(:gl_project) { FactoryGirl.create :empty_project, gitlab_ci_project: project } let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project } diff --git a/spec/models/ci/project_services/hip_chat_message_spec.rb b/spec/models/ci/project_services/hip_chat_message_spec.rb index e23d6ae2c28..7d54b6cf84c 100644 --- a/spec/models/ci/project_services/hip_chat_message_spec.rb +++ b/spec/models/ci/project_services/hip_chat_message_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Ci::HipChatMessage do +describe Ci::HipChatMessage, models: true do subject { Ci::HipChatMessage.new(build) } let(:commit) { FactoryGirl.create(:ci_commit_with_two_jobs) } diff --git a/spec/models/ci/project_services/hip_chat_service_spec.rb b/spec/models/ci/project_services/hip_chat_service_spec.rb index d9ccc855edf..714f1e17e0b 100644 --- a/spec/models/ci/project_services/hip_chat_service_spec.rb +++ b/spec/models/ci/project_services/hip_chat_service_spec.rb @@ -15,7 +15,7 @@ require 'spec_helper' -describe Ci::HipChatService do +describe Ci::HipChatService, models: true do describe "Validations" do diff --git a/spec/models/ci/project_services/mail_service_spec.rb b/spec/models/ci/project_services/mail_service_spec.rb index d9b3d34ff15..638d9a4a626 100644 --- a/spec/models/ci/project_services/mail_service_spec.rb +++ b/spec/models/ci/project_services/mail_service_spec.rb @@ -14,7 +14,7 @@ require 'spec_helper' -describe Ci::MailService do +describe Ci::MailService, models: true do describe "Associations" do it { is_expected.to belong_to :project } end @@ -44,13 +44,10 @@ describe Ci::MailService do end it do - should_email("git@example.com") - mail.execute(build) - end - - def should_email(email) - expect(Ci::Notify).to receive(:build_fail_email).with(build.id, email) - expect(Ci::Notify).not_to receive(:build_success_email).with(build.id, email) + perform_enqueued_jobs do + expect{ mail.execute(build) }.to change{ ActionMailer::Base.deliveries.size }.by(1) + expect(ActionMailer::Base.deliveries.last.to).to eq(["git@example.com"]) + end end end @@ -67,13 +64,10 @@ describe Ci::MailService do end it do - should_email("git@example.com") - mail.execute(build) - end - - def should_email(email) - expect(Ci::Notify).to receive(:build_success_email).with(build.id, email) - expect(Ci::Notify).not_to receive(:build_fail_email).with(build.id, email) + perform_enqueued_jobs do + expect{ mail.execute(build) }.to change{ ActionMailer::Base.deliveries.size }.by(1) + expect(ActionMailer::Base.deliveries.last.to).to eq(["git@example.com"]) + end end end @@ -95,14 +89,12 @@ describe Ci::MailService do end it do - should_email("git@example.com") - should_email("jeroen@example.com") - mail.execute(build) - end - - def should_email(email) - expect(Ci::Notify).to receive(:build_success_email).with(build.id, email) - expect(Ci::Notify).not_to receive(:build_fail_email).with(build.id, email) + perform_enqueued_jobs do + expect{ mail.execute(build) }.to change{ ActionMailer::Base.deliveries.size }.by(2) + expect( + ActionMailer::Base.deliveries.map(&:to).flatten + ).to include("git@example.com", "jeroen@example.com") + end end end @@ -124,14 +116,11 @@ describe Ci::MailService do end it do - should_email(commit.git_author_email) - should_email("jeroen@example.com") - mail.execute(build) if mail.can_execute?(build) - end - - def should_email(email) - expect(Ci::Notify).not_to receive(:build_success_email).with(build.id, email) - expect(Ci::Notify).not_to receive(:build_fail_email).with(build.id, email) + perform_enqueued_jobs do + expect do + mail.execute(build) if mail.can_execute?(build) + end.to_not change{ ActionMailer::Base.deliveries.size } + end end end @@ -177,14 +166,11 @@ describe Ci::MailService do it do Ci::Build.retry(build) - should_email(commit.git_author_email) - should_email("jeroen@example.com") - mail.execute(build) if mail.can_execute?(build) - end - - def should_email(email) - expect(Ci::Notify).not_to receive(:build_success_email).with(build.id, email) - expect(Ci::Notify).not_to receive(:build_fail_email).with(build.id, email) + perform_enqueued_jobs do + expect do + mail.execute(build) if mail.can_execute?(build) + end.to_not change{ ActionMailer::Base.deliveries.size } + end end end end diff --git a/spec/models/ci/project_services/slack_message_spec.rb b/spec/models/ci/project_services/slack_message_spec.rb index 8adda6c86cc..226032b4cda 100644 --- a/spec/models/ci/project_services/slack_message_spec.rb +++ b/spec/models/ci/project_services/slack_message_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Ci::SlackMessage do +describe Ci::SlackMessage, models: true do subject { Ci::SlackMessage.new(commit) } let(:commit) { FactoryGirl.create(:ci_commit_with_two_jobs) } diff --git a/spec/models/ci/project_services/slack_service_spec.rb b/spec/models/ci/project_services/slack_service_spec.rb index 1ac7dfe568d..e7d7d5d6f4c 100644 --- a/spec/models/ci/project_services/slack_service_spec.rb +++ b/spec/models/ci/project_services/slack_service_spec.rb @@ -14,7 +14,7 @@ require 'spec_helper' -describe Ci::SlackService do +describe Ci::SlackService, models: true do describe "Associations" do it { is_expected.to belong_to :project } end diff --git a/spec/models/ci/project_spec.rb b/spec/models/ci/project_spec.rb index 490c6a67982..346471aa9b5 100644 --- a/spec/models/ci/project_spec.rb +++ b/spec/models/ci/project_spec.rb @@ -1,9 +1,9 @@ # == Schema Information # -# Table name: projects +# Table name: ci_projects # # id :integer not null, primary key -# name :string(255) not null +# name :string(255) # timeout :integer default(3600), not null # created_at :datetime # updated_at :datetime @@ -27,9 +27,9 @@ require 'spec_helper' -describe Ci::Project do - let(:gl_project) { FactoryGirl.create :empty_project } - let(:project) { FactoryGirl.create :ci_project, gl_project: gl_project } +describe Ci::Project, models: true do + let(:project) { FactoryGirl.create :ci_project } + let(:gl_project) { project.gl_project } subject { project } it { is_expected.to have_many(:runner_projects) } @@ -194,18 +194,6 @@ describe Ci::Project do end end - describe 'Project.parse' do - let(:project) { FactoryGirl.create :project } - - subject { Ci::Project.parse(project) } - - it { is_expected.to be_valid } - it { is_expected.to be_kind_of(Ci::Project) } - it { expect(subject.name).to eq(project.name_with_namespace) } - it { expect(subject.gitlab_id).to eq(project.id) } - it { expect(subject.gitlab_url).to eq(project.web_url) } - end - describe :repo_url_with_auth do let(:project) { FactoryGirl.create :ci_project } subject { project.repo_url_with_auth } diff --git a/spec/models/ci/runner_project_spec.rb b/spec/models/ci/runner_project_spec.rb index 0218d484130..da8491357a5 100644 --- a/spec/models/ci/runner_project_spec.rb +++ b/spec/models/ci/runner_project_spec.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: runner_projects +# Table name: ci_runner_projects # # id :integer not null, primary key # runner_id :integer not null @@ -11,6 +11,6 @@ require 'spec_helper' -describe Ci::RunnerProject do +describe Ci::RunnerProject, models: true do pending "add some examples to (or delete) #{__FILE__}" end diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index f8a51c29dc2..6ebb5e86863 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: runners +# Table name: ci_runners # # id :integer not null, primary key # token :string(255) @@ -19,7 +19,7 @@ require 'spec_helper' -describe Ci::Runner do +describe Ci::Runner, models: true do describe '#display_name' do it 'should return the description if it has a value' do runner = FactoryGirl.build(:ci_runner, description: 'Linux/Ruby-1.9.3-p448') diff --git a/spec/models/ci/service_spec.rb b/spec/models/ci/service_spec.rb index 2df70e88888..34e3af7f810 100644 --- a/spec/models/ci/service_spec.rb +++ b/spec/models/ci/service_spec.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: services +# Table name: ci_services # # id :integer not null, primary key # type :string(255) @@ -14,7 +14,7 @@ require 'spec_helper' -describe Ci::Service do +describe Ci::Service, models: true do describe "Associations" do it { is_expected.to belong_to :project } diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb index 19c14ef2da2..61eb3c08296 100644 --- a/spec/models/ci/trigger_spec.rb +++ b/spec/models/ci/trigger_spec.rb @@ -1,6 +1,18 @@ +# == Schema Information +# +# Table name: ci_triggers +# +# id :integer not null, primary key +# token :string(255) +# project_id :integer not null +# deleted_at :datetime +# created_at :datetime +# updated_at :datetime +# + require 'spec_helper' -describe Ci::Trigger do +describe Ci::Trigger, models: true do let(:project) { FactoryGirl.create :ci_project } describe 'before_validation' do diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb index d034a6c7b9f..31b56953a13 100644 --- a/spec/models/ci/variable_spec.rb +++ b/spec/models/ci/variable_spec.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: variables +# Table name: ci_variables # # id :integer not null, primary key # project_id :integer not null @@ -13,7 +13,7 @@ require 'spec_helper' -describe Ci::Variable do +describe Ci::Variable, models: true do subject { Ci::Variable.new } let(:secret_value) { 'secret' } diff --git a/spec/models/ci/web_hook_spec.rb b/spec/models/ci/web_hook_spec.rb index bf9481ab81d..1a4edec9d4f 100644 --- a/spec/models/ci/web_hook_spec.rb +++ b/spec/models/ci/web_hook_spec.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: web_hooks +# Table name: ci_web_hooks # # id :integer not null, primary key # url :string(255) not null @@ -11,7 +11,7 @@ require 'spec_helper' -describe Ci::WebHook do +describe Ci::WebHook, models: true do describe "Associations" do it { is_expected.to belong_to :project } end diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb index 1031af097bd..9307d97e214 100644 --- a/spec/models/commit_range_spec.rb +++ b/spec/models/commit_range_spec.rb @@ -1,56 +1,78 @@ require 'spec_helper' -describe CommitRange do +describe CommitRange, models: true do describe 'modules' do subject { described_class } it { is_expected.to include_module(Referable) } end - let(:sha_from) { 'f3f85602' } - let(:sha_to) { 'e86e1013' } + let!(:project) { create(:project, :public) } + let!(:commit1) { project.commit("HEAD~2") } + let!(:commit2) { project.commit } - let(:range) { described_class.new("#{sha_from}...#{sha_to}") } - let(:range2) { described_class.new("#{sha_from}..#{sha_to}") } + let(:sha_from) { commit1.short_id } + let(:sha_to) { commit2.short_id } + + let(:full_sha_from) { commit1.id } + let(:full_sha_to) { commit2.id } + + let(:range) { described_class.new("#{sha_from}...#{sha_to}", project) } + let(:range2) { described_class.new("#{sha_from}..#{sha_to}", project) } it 'raises ArgumentError when given an invalid range string' do - expect { described_class.new("Foo") }.to raise_error(ArgumentError) + expect { described_class.new("Foo", project) }.to raise_error(ArgumentError) end describe '#to_s' do it 'is correct for three-dot syntax' do - expect(range.to_s).to eq "#{sha_from[0..7]}...#{sha_to[0..7]}" + expect(range.to_s).to eq "#{full_sha_from}...#{full_sha_to}" end it 'is correct for two-dot syntax' do - expect(range2.to_s).to eq "#{sha_from[0..7]}..#{sha_to[0..7]}" + expect(range2.to_s).to eq "#{full_sha_from}..#{full_sha_to}" end end describe '#to_reference' do - let(:project) { double('project', to_reference: 'namespace1/project') } + let(:cross) { create(:project) } + + it 'returns a String reference to the object' do + expect(range.to_reference).to eq "#{full_sha_from}...#{full_sha_to}" + end + + it 'returns a String reference to the object' do + expect(range2.to_reference).to eq "#{full_sha_from}..#{full_sha_to}" + end + + it 'supports a cross-project reference' do + expect(range.to_reference(cross)).to eq "#{project.to_reference}@#{full_sha_from}...#{full_sha_to}" + end + end - before do - range.project = project + describe '#reference_link_text' do + let(:cross) { create(:project) } + + it 'returns a String reference to the object' do + expect(range.reference_link_text).to eq "#{sha_from}...#{sha_to}" end it 'returns a String reference to the object' do - expect(range.to_reference).to eq range.to_s + expect(range2.reference_link_text).to eq "#{sha_from}..#{sha_to}" end it 'supports a cross-project reference' do - cross = double('project') - expect(range.to_reference(cross)).to eq "#{project.to_reference}@#{range.to_s}" + expect(range.reference_link_text(cross)).to eq "#{project.to_reference}@#{sha_from}...#{sha_to}" end end describe '#reference_title' do it 'returns the correct String for three-dot ranges' do - expect(range.reference_title).to eq "Commits #{sha_from} through #{sha_to}" + expect(range.reference_title).to eq "Commits #{full_sha_from} through #{full_sha_to}" end it 'returns the correct String for two-dot ranges' do - expect(range2.reference_title).to eq "Commits #{sha_from}^ through #{sha_to}" + expect(range2.reference_title).to eq "Commits #{full_sha_from}^ through #{full_sha_to}" end end @@ -60,11 +82,11 @@ describe CommitRange do end it 'includes the correct values for a three-dot range' do - expect(range.to_param).to eq({ from: sha_from, to: sha_to }) + expect(range.to_param).to eq({ from: full_sha_from, to: full_sha_to }) end it 'includes the correct values for a two-dot range' do - expect(range2.to_param).to eq({ from: sha_from + '^', to: sha_to }) + expect(range2.to_param).to eq({ from: full_sha_from + '^', to: full_sha_to }) end end @@ -79,64 +101,37 @@ describe CommitRange do end describe '#valid_commits?' do - context 'without a project' do - it 'returns nil' do - expect(range.valid_commits?).to be_nil + context 'with a valid repo' do + before do + expect(project).to receive(:valid_repo?).and_return(true) end - end - - it 'accepts an optional project argument' do - project1 = double('project1').as_null_object - project2 = double('project2').as_null_object - - # project1 gets assigned through the accessor, but ignored when not given - # as an argument to `valid_commits?` - expect(project1).not_to receive(:present?) - range.project = project1 - - # project2 gets passed to `valid_commits?` - expect(project2).to receive(:present?).and_return(false) - range.valid_commits?(project2) - end - - context 'with a project' do - let(:project) { double('project', repository: double('repository')) } + it 'is false when `sha_from` is invalid' do + expect(project).to receive(:commit).with(sha_from).and_return(nil) + expect(project).to receive(:commit).with(sha_to).and_call_original - context 'with a valid repo' do - before do - expect(project).to receive(:valid_repo?).and_return(true) - range.project = project - end + expect(range).not_to be_valid_commits + end - it 'is false when `sha_from` is invalid' do - expect(project.repository).to receive(:commit).with(sha_from).and_return(false) - expect(project.repository).not_to receive(:commit).with(sha_to) - expect(range).not_to be_valid_commits - end + it 'is false when `sha_to` is invalid' do + expect(project).to receive(:commit).with(sha_from).and_call_original + expect(project).to receive(:commit).with(sha_to).and_return(nil) - it 'is false when `sha_to` is invalid' do - expect(project.repository).to receive(:commit).with(sha_from).and_return(true) - expect(project.repository).to receive(:commit).with(sha_to).and_return(false) - expect(range).not_to be_valid_commits - end + expect(range).not_to be_valid_commits + end - it 'is true when both `sha_from` and `sha_to` are valid' do - expect(project.repository).to receive(:commit).with(sha_from).and_return(true) - expect(project.repository).to receive(:commit).with(sha_to).and_return(true) - expect(range).to be_valid_commits - end + it 'is true when both `sha_from` and `sha_to` are valid' do + expect(range).to be_valid_commits end + end - context 'without a valid repo' do - before do - expect(project).to receive(:valid_repo?).and_return(false) - range.project = project - end + context 'without a valid repo' do + before do + expect(project).to receive(:valid_repo?).and_return(false) + end - it 'returns false' do - expect(range).not_to be_valid_commits - end + it 'returns false' do + expect(range).not_to be_valid_commits end end end diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 90be9324951..ecf37b40c58 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Commit do +describe Commit, models: true do let(:project) { create(:project) } let(:commit) { project.commit } @@ -24,6 +24,17 @@ describe Commit do end end + describe '#reference_link_text' do + it 'returns a String reference to the object' do + expect(commit.reference_link_text).to eq commit.short_id + end + + it 'supports a cross-project reference' do + cross = double('project') + expect(commit.reference_link_text(cross)).to eq "#{project.to_reference}@#{commit.short_id}" + end + end + describe '#title' do it "returns no_commit_message when safe_message is blank" do allow(commit).to receive(:safe_message).and_return('') @@ -77,14 +88,10 @@ eos let(:other_issue) { create :issue, project: other_project } it 'detects issues that this commit is marked as closing' do - allow(commit).to receive(:safe_message).and_return("Fixes ##{issue.iid}") - expect(commit.closes_issues).to eq([issue]) - end - - it 'does not detect issues from other projects' do ext_ref = "#{other_project.path_with_namespace}##{other_issue.iid}" - allow(commit).to receive(:safe_message).and_return("Fixes #{ext_ref}") - expect(commit.closes_issues).to be_empty + allow(commit).to receive(:safe_message).and_return("Fixes ##{issue.iid} and #{ext_ref}") + expect(commit.closes_issues).to include(issue) + expect(commit.closes_issues).to include(other_issue) end end @@ -100,4 +107,15 @@ eos # Include the subject in the repository stub. let(:extra_commits) { [subject] } end + + describe '#hook_attrs' do + let(:data) { commit.hook_attrs(with_changed_files: true) } + + it { expect(data).to be_a(Hash) } + it { expect(data[:message]).to include('Add submodule from gitlab.com') } + it { expect(data[:timestamp]).to eq('2014-02-27T11:01:38+02:00') } + it { expect(data[:added]).to eq(["gitlab-grack"]) } + it { expect(data[:modified]).to eq([".gitmodules"]) } + it { expect(data[:removed]).to eq([]) } + end end diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index c96a606fdaa..5e311ead28b 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -1,6 +1,39 @@ +# == Schema Information +# +# Table name: ci_builds +# +# id :integer not null, primary key +# project_id :integer +# status :string(255) +# finished_at :datetime +# trace :text +# created_at :datetime +# updated_at :datetime +# started_at :datetime +# runner_id :integer +# coverage :float +# commit_id :integer +# commands :text +# job_id :integer +# name :string(255) +# deploy :boolean default(FALSE) +# options :text +# allow_failure :boolean default(FALSE), not null +# stage :string(255) +# trigger_request_id :integer +# stage_idx :integer +# tag :boolean +# ref :string(255) +# user_id :integer +# type :string(255) +# target_url :string(255) +# description :string(255) +# artifacts_file :text +# + require 'spec_helper' -describe CommitStatus do +describe CommitStatus, models: true do let(:commit) { FactoryGirl.create :ci_commit } let(:commit_status) { FactoryGirl.create :commit_status, commit: commit } diff --git a/spec/models/concerns/case_sensitivity_spec.rb b/spec/models/concerns/case_sensitivity_spec.rb index f7ed30f8198..25b3f4e50da 100644 --- a/spec/models/concerns/case_sensitivity_spec.rb +++ b/spec/models/concerns/case_sensitivity_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe CaseSensitivity do +describe CaseSensitivity, models: true do describe '.iwhere' do let(:connection) { ActiveRecord::Base.connection } let(:model) { Class.new { include CaseSensitivity } } diff --git a/spec/models/concerns/strip_attribute_spec.rb b/spec/models/concerns/strip_attribute_spec.rb new file mode 100644 index 00000000000..6445e29c3ef --- /dev/null +++ b/spec/models/concerns/strip_attribute_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe Milestone, "StripAttribute" do + let(:milestone) { create(:milestone) } + + describe ".strip_attributes" do + it { expect(Milestone).to respond_to(:strip_attributes) } + it { expect(Milestone.strip_attrs).to include(:title) } + end + + describe "#strip_attributes" do + before do + milestone.title = ' 8.3 ' + milestone.valid? + end + + it { expect(milestone.title).to eq('8.3') } + end + +end diff --git a/spec/models/deploy_key_spec.rb b/spec/models/deploy_key_spec.rb index 95729932459..64ba778afea 100644 --- a/spec/models/deploy_key_spec.rb +++ b/spec/models/deploy_key_spec.rb @@ -15,7 +15,7 @@ require 'spec_helper' -describe DeployKey do +describe DeployKey, models: true do let(:project) { create(:project) } let(:deploy_key) { create(:deploy_key, projects: [project]) } diff --git a/spec/models/deploy_keys_project_spec.rb b/spec/models/deploy_keys_project_spec.rb index 0eb22599d18..8aedbfb8636 100644 --- a/spec/models/deploy_keys_project_spec.rb +++ b/spec/models/deploy_keys_project_spec.rb @@ -11,7 +11,7 @@ require 'spec_helper' -describe DeployKeysProject do +describe DeployKeysProject, models: true do describe "Associations" do it { is_expected.to belong_to(:deploy_key) } it { is_expected.to belong_to(:project) } diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 0f32f162a10..071582b0282 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -16,7 +16,7 @@ require 'spec_helper' -describe Event do +describe Event, models: true do describe "Associations" do it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:target) } @@ -64,4 +64,42 @@ describe Event do it { expect(@event.branch_name).to eq("master") } it { expect(@event.author).to eq(@user) } end + + describe '.latest_update_time' do + describe 'when events are present' do + let(:time) { Time.utc(2015, 1, 1) } + + before do + create(:closed_issue_event, updated_at: time) + create(:closed_issue_event, updated_at: time + 5) + end + + it 'returns the latest update time' do + expect(Event.latest_update_time).to eq(time + 5) + end + end + + describe 'when no events exist' do + it 'returns nil' do + expect(Event.latest_update_time).to be_nil + end + end + end + + describe '.limit_recent' do + let!(:event1) { create(:closed_issue_event) } + let!(:event2) { create(:closed_issue_event) } + + describe 'without an explicit limit' do + subject { Event.limit_recent } + + it { is_expected.to eq([event2, event1]) } + end + + describe 'with an explicit limit' do + subject { Event.limit_recent(1) } + + it { is_expected.to eq([event2]) } + end + end end diff --git a/spec/models/external_issue_spec.rb b/spec/models/external_issue_spec.rb index 7744610db78..6ec6b9037a4 100644 --- a/spec/models/external_issue_spec.rb +++ b/spec/models/external_issue_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe ExternalIssue do +describe ExternalIssue, models: true do let(:project) { double('project', to_reference: 'namespace1/project1') } let(:issue) { described_class.new('EXT-1234', project) } diff --git a/spec/models/external_wiki_service_spec.rb b/spec/models/external_wiki_service_spec.rb index 4bd5b0be61c..b198aa77526 100644 --- a/spec/models/external_wiki_service_spec.rb +++ b/spec/models/external_wiki_service_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' -describe ExternalWikiService do +describe ExternalWikiService, models: true do include ExternalWikiHelper describe "Associations" do it { should belong_to :project } diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb index f442fa5fbe5..d61c1c96bde 100644 --- a/spec/models/generic_commit_status_spec.rb +++ b/spec/models/generic_commit_status_spec.rb @@ -1,6 +1,39 @@ +# == Schema Information +# +# Table name: ci_builds +# +# id :integer not null, primary key +# project_id :integer +# status :string(255) +# finished_at :datetime +# trace :text +# created_at :datetime +# updated_at :datetime +# started_at :datetime +# runner_id :integer +# coverage :float +# commit_id :integer +# commands :text +# job_id :integer +# name :string(255) +# deploy :boolean default(FALSE) +# options :text +# allow_failure :boolean default(FALSE), not null +# stage :string(255) +# trigger_request_id :integer +# stage_idx :integer +# tag :boolean +# ref :string(255) +# user_id :integer +# type :string(255) +# target_url :string(255) +# description :string(255) +# artifacts_file :text +# + require 'spec_helper' -describe GenericCommitStatus do +describe GenericCommitStatus, models: true do let(:commit) { FactoryGirl.create :ci_commit } let(:generic_commit_status) { FactoryGirl.create :generic_commit_status, commit: commit } diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb new file mode 100644 index 00000000000..ba03e6aabd0 --- /dev/null +++ b/spec/models/global_milestone_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper' + +describe GlobalMilestone, models: true do + let(:user) { create(:user) } + let(:user2) { create(:user) } + let(:group) { create(:group) } + let(:project1) { create(:project, group: group) } + let(:project2) { create(:project, path: 'gitlab-ci', group: group) } + let(:project3) { create(:project, path: 'cookbook-gitlab', group: group) } + let(:milestone1_project1) { create(:milestone, title: "Milestone v1.2", project: project1) } + let(:milestone1_project2) { create(:milestone, title: "Milestone v1.2", project: project2) } + let(:milestone1_project3) { create(:milestone, title: "Milestone v1.2", project: project3) } + let(:milestone2_project1) { create(:milestone, title: "VD-123", project: project1) } + let(:milestone2_project2) { create(:milestone, title: "VD-123", project: project2) } + let(:milestone2_project3) { create(:milestone, title: "VD-123", project: project3) } + + describe :build_collection do + before do + milestones = + [ + milestone1_project1, + milestone1_project2, + milestone1_project3, + milestone2_project1, + milestone2_project2, + milestone2_project3 + ] + + @global_milestones = GlobalMilestone.build_collection(milestones) + end + + it 'should have all project milestones' do + expect(@global_milestones.count).to eq(2) + end + + it 'should have all project milestones titles' do + expect(@global_milestones.map(&:title)).to match_array(['Milestone v1.2', 'VD-123']) + end + + it 'should have all project milestones' do + expect(@global_milestones.map { |group_milestone| group_milestone.milestones.count }.sum).to eq(6) + end + end + + describe :initialize do + before do + milestones = + [ + milestone1_project1, + milestone1_project2, + milestone1_project3, + ] + + @global_milestone = GlobalMilestone.new(milestone1_project1.title, milestones) + end + + it 'should have exactly one group milestone' do + expect(@global_milestone.title).to eq('Milestone v1.2') + end + + it 'should have all project milestones with the same title' do + expect(@global_milestone.milestones.count).to eq(3) + end + end +end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 0f23e81ace9..646f767e6fe 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -11,11 +11,12 @@ # type :string(255) # description :string(255) default(""), not null # avatar :string(255) +# public :boolean default(FALSE) # require 'spec_helper' -describe Group do +describe Group, models: true do let!(:group) { create(:group) } describe 'associations' do @@ -37,6 +38,33 @@ describe Group do it { is_expected.not_to validate_presence_of :owner } end + describe '.public_and_given_groups' do + let!(:public_group) { create(:group, public: true) } + + subject { described_class.public_and_given_groups([group.id]) } + + it { is_expected.to eq([public_group, group]) } + end + + describe '.visible_to_user' do + let!(:group) { create(:group) } + let!(:user) { create(:user) } + + subject { described_class.visible_to_user(user) } + + describe 'when the user has access to a group' do + before do + group.add_user(user, Gitlab::Access::MASTER) + end + + it { is_expected.to eq([group]) } + end + + describe 'when the user does not have access to any groups' do + it { is_expected.to eq([]) } + end + end + describe '#to_reference' do it 'returns a String reference to the object' do expect(group.to_reference).to eq "@#{group.name}" diff --git a/spec/models/hooks/project_hook_spec.rb b/spec/models/hooks/project_hook_spec.rb index a2dc66fce3e..645ee0b929a 100644 --- a/spec/models/hooks/project_hook_spec.rb +++ b/spec/models/hooks/project_hook_spec.rb @@ -18,7 +18,7 @@ require 'spec_helper' -describe ProjectHook do +describe ProjectHook, models: true do describe '.push_hooks' do it 'should return hooks for push events only' do hook = create(:project_hook, push_events: true) diff --git a/spec/models/hooks/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb index 16641c12124..1455661485b 100644 --- a/spec/models/hooks/service_hook_spec.rb +++ b/spec/models/hooks/service_hook_spec.rb @@ -18,7 +18,7 @@ require "spec_helper" -describe ServiceHook do +describe ServiceHook, models: true do describe "Associations" do it { is_expected.to belong_to :service } end diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb index 02d2cc2c77a..138b87a9a06 100644 --- a/spec/models/hooks/system_hook_spec.rb +++ b/spec/models/hooks/system_hook_spec.rb @@ -18,7 +18,7 @@ require "spec_helper" -describe SystemHook do +describe SystemHook, models: true do describe "execute" do before(:each) do @system_hook = create(:system_hook) diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb index 2fdc49f02ee..2d90b0793cc 100644 --- a/spec/models/hooks/web_hook_spec.rb +++ b/spec/models/hooks/web_hook_spec.rb @@ -18,7 +18,7 @@ require 'spec_helper' -describe ProjectHook do +describe ProjectHook, models: true do describe "Associations" do it { is_expected.to belong_to :project } end @@ -71,5 +71,11 @@ describe ProjectHook do expect { @project_hook.execute(@data, 'push_hooks') }.to raise_error(RuntimeError) end + + it "handles SSL exceptions" do + expect(WebHook).to receive(:post).and_raise(OpenSSL::SSL::SSLError.new('SSL error')) + + expect(@project_hook.execute(@data, 'push_hooks')).to eq([false, 'SSL error']) + end end end diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index c9aa1b063c6..52271c7c8c6 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' -describe Issue do +describe Issue, models: true do describe "Associations" do it { is_expected.to belong_to(:milestone) } end diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb index 2f819f60cbb..d7fe01976d8 100644 --- a/spec/models/key_spec.rb +++ b/spec/models/key_spec.rb @@ -15,7 +15,7 @@ require 'spec_helper' -describe Key do +describe Key, models: true do describe "Associations" do it { is_expected.to belong_to(:user) } end diff --git a/spec/models/label_link_spec.rb b/spec/models/label_link_spec.rb index 8c240826582..dc7510b1de3 100644 --- a/spec/models/label_link_spec.rb +++ b/spec/models/label_link_spec.rb @@ -12,7 +12,7 @@ require 'spec_helper' -describe LabelLink do +describe LabelLink, models: true do let(:label) { create(:label_link) } it { expect(label).to be_valid } diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb index 6518213d71c..696fbf7e0aa 100644 --- a/spec/models/label_spec.rb +++ b/spec/models/label_spec.rb @@ -8,11 +8,12 @@ # project_id :integer # created_at :datetime # updated_at :datetime +# template :boolean default(FALSE) # require 'spec_helper' -describe Label do +describe Label, models: true do let(:label) { create(:label) } describe 'associations' do diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 57f840c1e91..2aedca20df2 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -19,7 +19,7 @@ require 'spec_helper' -describe Member do +describe Member, models: true do describe "Associations" do it { is_expected.to belong_to(:user) } end diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb index 652026729bb..5424c9b9cba 100644 --- a/spec/models/members/group_member_spec.rb +++ b/spec/models/members/group_member_spec.rb @@ -19,7 +19,7 @@ require 'spec_helper' -describe GroupMember do +describe GroupMember, models: true do context 'notification' do describe "#after_create" do it "should send email to user" do diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index ee912bf12a2..9f26d9eb5ce 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -19,7 +19,7 @@ require 'spec_helper' -describe ProjectMember do +describe ProjectMember, models: true do describe :import_team do before do @abilities = Six.new diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index eed2cbc5412..1aeba9b2b3b 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -20,17 +20,18 @@ # position :integer default(0) # locked_at :datetime # updated_by_id :integer +# merge_error :string(255) # require 'spec_helper' -describe MergeRequest do +describe MergeRequest, models: true do subject { create(:merge_request) } describe 'associations' do it { is_expected.to belong_to(:target_project).with_foreign_key(:target_project_id).class_name('Project') } it { is_expected.to belong_to(:source_project).with_foreign_key(:source_project_id).class_name('Project') } - + it { is_expected.to belong_to(:merge_user).class_name("User") } it { is_expected.to have_one(:merge_request_diff).dependent(:destroy) } end @@ -47,12 +48,32 @@ describe MergeRequest do describe 'validation' do it { is_expected.to validate_presence_of(:target_branch) } it { is_expected.to validate_presence_of(:source_branch) } + + context "Validation of merge user with Merge When Build succeeds" do + it "allows user to be nil when the feature is disabled" do + expect(subject).to be_valid + end + + it "is invalid without merge user" do + subject.merge_when_build_succeeds = true + expect(subject).not_to be_valid + end + + it "is valid with merge user" do + subject.merge_when_build_succeeds = true + subject.merge_user = build(:user) + + expect(subject).to be_valid + end + end end describe 'respond to' do it { is_expected.to respond_to(:unchecked?) } it { is_expected.to respond_to(:can_be_merged?) } it { is_expected.to respond_to(:cannot_be_merged?) } + it { is_expected.to respond_to(:merge_params) } + it { is_expected.to respond_to(:merge_when_build_succeeds) } end describe '#to_reference' do @@ -171,6 +192,50 @@ describe MergeRequest do end end + describe '#can_remove_source_branch?' do + let(:user) { create(:user) } + let(:user2) { create(:user) } + + before do + subject.source_project.team << [user, :master] + + subject.source_branch = "feature" + subject.target_branch = "master" + subject.save! + end + + it "can't be removed when its a protected branch" do + allow(subject.source_project).to receive(:protected_branch?).and_return(true) + expect(subject.can_remove_source_branch?(user)).to be_falsey + end + + it "cant remove a root ref" do + subject.source_branch = "master" + subject.target_branch = "feature" + + expect(subject.can_remove_source_branch?(user)).to be_falsey + end + + it "is unable to remove the source branch for a project the user cannot push to" do + expect(subject.can_remove_source_branch?(user2)).to be_falsey + end + + it "is can be removed in all other cases" do + expect(subject.can_remove_source_branch?(user)).to be_truthy + end + end + + describe "#reset_merge_when_build_succeeds" do + let(:merge_if_green) { create :merge_request, merge_when_build_succeeds: true, merge_user: create(:user) } + + it "sets the item to false" do + merge_if_green.reset_merge_when_build_succeeds + merge_if_green.reload + + expect(merge_if_green.merge_when_build_succeeds).to be_falsey + end + end + describe "#hook_attrs" do it "has all the required keys" do attrs = subject.hook_attrs @@ -192,4 +257,29 @@ describe MergeRequest do it_behaves_like 'a Taskable' do subject { create :merge_request, :simple } end + + describe '#ci_commit' do + describe 'when the source project exists' do + it 'returns the latest commit' do + commit = double(:commit, id: '123abc') + ci_commit = double(:ci_commit) + + allow(subject).to receive(:last_commit).and_return(commit) + + expect(subject.source_project).to receive(:ci_commit). + with('123abc'). + and_return(ci_commit) + + expect(subject.ci_commit).to eq(ci_commit) + end + end + + describe 'when the source project does not exist' do + it 'returns nil' do + allow(subject).to receive(:source_project).and_return(nil) + + expect(subject.ci_commit).to be_nil + end + end + end end diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index 77c58627322..30a71987d86 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -15,7 +15,7 @@ require 'spec_helper' -describe Milestone do +describe Milestone, models: true do describe "Associations" do it { is_expected.to belong_to(:project) } it { is_expected.to have_many(:issues) } diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 1d72a9503ae..4fa2d2bc4d2 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -11,11 +11,12 @@ # type :string(255) # description :string(255) default(""), not null # avatar :string(255) +# public :boolean default(FALSE) # require 'spec_helper' -describe Namespace do +describe Namespace, models: true do let!(:namespace) { create(:namespace) } it { is_expected.to have_many :projects } diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 75564839dcf..cd3c868ecc5 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -16,11 +16,12 @@ # system :boolean default(FALSE), not null # st_diff :text # updated_by_id :integer +# is_award :boolean default(FALSE), not null # require 'spec_helper' -describe Note do +describe Note, models: true do describe 'associations' do it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:noteable) } @@ -32,77 +33,6 @@ describe Note do it { is_expected.to validate_presence_of(:project) } end - describe '#votable?' do - it 'is true for issue notes' do - note = build(:note_on_issue) - expect(note).to be_votable - end - - it 'is true for merge request notes' do - note = build(:note_on_merge_request) - expect(note).to be_votable - end - - it 'is false for merge request diff notes' do - note = build(:note_on_merge_request_diff) - expect(note).not_to be_votable - end - - it 'is false for commit notes' do - note = build(:note_on_commit) - expect(note).not_to be_votable - end - - it 'is false for commit diff notes' do - note = build(:note_on_commit_diff) - expect(note).not_to be_votable - end - end - - describe 'voting score' do - it 'recognizes a neutral note' do - note = build(:votable_note, note: 'This is not a +1 note') - expect(note).not_to be_upvote - expect(note).not_to be_downvote - end - - it 'recognizes a neutral emoji note' do - note = build(:votable_note, note: "I would :+1: this, but I don't want to") - expect(note).not_to be_upvote - expect(note).not_to be_downvote - end - - it 'recognizes a +1 note' do - note = build(:votable_note, note: '+1 for this') - expect(note).to be_upvote - end - - it 'recognizes a +1 emoji as a vote' do - note = build(:votable_note, note: ':+1: for this') - expect(note).to be_upvote - end - - it 'recognizes a thumbsup emoji as a vote' do - note = build(:votable_note, note: ':thumbsup: for this') - expect(note).to be_upvote - end - - it 'recognizes a -1 note' do - note = build(:votable_note, note: '-1 for this') - expect(note).to be_downvote - end - - it 'recognizes a -1 emoji as a vote' do - note = build(:votable_note, note: ':-1: for this') - expect(note).to be_downvote - end - - it 'recognizes a thumbsdown emoji as a vote' do - note = build(:votable_note, note: ':thumbsdown: for this') - expect(note).to be_downvote - end - end - describe "Commit notes" do let!(:note) { create(:note_on_commit, note: "+1 from me") } let!(:commit) { note.noteable } @@ -139,10 +69,6 @@ describe Note do it "should be recognized by #for_commit_diff_line?" do expect(note).to be_for_commit_diff_line end - - it "should not be votable" do - expect(note).not_to be_votable - end end describe 'authorization' do @@ -204,4 +130,16 @@ describe Note do it { expect(Note.search('wow')).to include(note) } end + + describe :grouped_awards do + before do + create :note, note: "smile", is_award: true + create :note, note: "smile", is_award: true + end + + it "returns grouped array of notes" do + expect(Note.grouped_awards.first.first).to eq("smile") + expect(Note.grouped_awards.first.last).to match_array(Note.all) + end + end end diff --git a/spec/models/project_security_spec.rb b/spec/models/project_security_spec.rb index f600a240c46..3643ad1b052 100644 --- a/spec/models/project_security_spec.rb +++ b/spec/models/project_security_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Project do +describe Project, models: true do describe :authorization do before do @p1 = create(:project) diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb index 230807ea672..88cd624877a 100644 --- a/spec/models/project_services/buildkite_service_spec.rb +++ b/spec/models/project_services/buildkite_service_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' -describe BuildkiteService do +describe BuildkiteService, models: true do describe 'Associations' do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb index e9967f5fe0b..a2cf68a9e38 100644 --- a/spec/models/project_services/drone_ci_service_spec.rb +++ b/spec/models/project_services/drone_ci_service_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' -describe DroneCiService do +describe DroneCiService, models: true do describe 'associations' do it { is_expected.to belong_to(:project) } it { is_expected.to have_one(:service_hook) } diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb index 16296607a94..ff7fbcaa004 100644 --- a/spec/models/project_services/flowdock_service_spec.rb +++ b/spec/models/project_services/flowdock_service_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' -describe FlowdockService do +describe FlowdockService, models: true do describe "Associations" do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } diff --git a/spec/models/project_services/gemnasium_service_spec.rb b/spec/models/project_services/gemnasium_service_spec.rb index 9e156472316..ecb3ccb1673 100644 --- a/spec/models/project_services/gemnasium_service_spec.rb +++ b/spec/models/project_services/gemnasium_service_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' -describe GemnasiumService do +describe GemnasiumService, models: true do describe "Associations" do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } diff --git a/spec/models/project_services/gitlab_ci_service_spec.rb b/spec/models/project_services/gitlab_ci_service_spec.rb index b9006b693b2..835bf364050 100644 --- a/spec/models/project_services/gitlab_ci_service_spec.rb +++ b/spec/models/project_services/gitlab_ci_service_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' -describe GitlabCiService do +describe GitlabCiService, models: true do describe 'associations' do it { is_expected.to belong_to(:project) } it { is_expected.to have_one(:service_hook) } diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb index e34ca09bffc..3518dbd1728 100644 --- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb +++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' -describe GitlabIssueTrackerService do +describe GitlabIssueTrackerService, models: true do describe "Associations" do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb index f67d7b30980..c96ab548149 100644 --- a/spec/models/project_services/hipchat_service_spec.rb +++ b/spec/models/project_services/hipchat_service_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' -describe HipchatService do +describe HipchatService, models: true do describe "Associations" do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb index 7d483a44c53..b783b1a576e 100644 --- a/spec/models/project_services/irker_service_spec.rb +++ b/spec/models/project_services/irker_service_spec.rb @@ -22,7 +22,7 @@ require 'spec_helper' require 'socket' require 'json' -describe IrkerService do +describe IrkerService, models: true do describe 'Associations' do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index ddd2cce212c..7d91ebe9ce6 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' -describe JiraService do +describe JiraService, models: true do describe "Associations" do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } @@ -94,9 +94,9 @@ describe JiraService do end it 'should be prepopulated with the settings' do - expect(@service.properties[:project_url]).to eq('http://jira.sample/projects/project_a') - expect(@service.properties[:issues_url]).to eq("http://jira.sample/issues/:id") - expect(@service.properties[:new_issue_url]).to eq("http://jira.sample/projects/project_a/issues/new") + expect(@service.properties["project_url"]).to eq('http://jira.sample/projects/project_a') + expect(@service.properties["issues_url"]).to eq("http://jira.sample/issues/:id") + expect(@service.properties["new_issue_url"]).to eq("http://jira.sample/projects/project_a/issues/new") end end end diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb index ac10ffbd39b..96039f9491b 100644 --- a/spec/models/project_services/pushover_service_spec.rb +++ b/spec/models/project_services/pushover_service_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' -describe PushoverService do +describe PushoverService, models: true do describe 'Associations' do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } diff --git a/spec/models/project_services/slack_service/issue_message_spec.rb b/spec/models/project_services/slack_service/issue_message_spec.rb index b78d92f23a1..97e6f03e308 100644 --- a/spec/models/project_services/slack_service/issue_message_spec.rb +++ b/spec/models/project_services/slack_service/issue_message_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe SlackService::IssueMessage do +describe SlackService::IssueMessage, models: true do subject { SlackService::IssueMessage.new(args) } let(:args) do diff --git a/spec/models/project_services/slack_service/merge_message_spec.rb b/spec/models/project_services/slack_service/merge_message_spec.rb index 581c50d6c88..dae8bd90922 100644 --- a/spec/models/project_services/slack_service/merge_message_spec.rb +++ b/spec/models/project_services/slack_service/merge_message_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe SlackService::MergeMessage do +describe SlackService::MergeMessage, models: true do subject { SlackService::MergeMessage.new(args) } let(:args) do diff --git a/spec/models/project_services/slack_service/note_message_spec.rb b/spec/models/project_services/slack_service/note_message_spec.rb index 21fb575480b..ebf8837570e 100644 --- a/spec/models/project_services/slack_service/note_message_spec.rb +++ b/spec/models/project_services/slack_service/note_message_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe SlackService::NoteMessage do +describe SlackService::NoteMessage, models: true do let(:color) { '#345' } before do diff --git a/spec/models/project_services/slack_service/push_message_spec.rb b/spec/models/project_services/slack_service/push_message_spec.rb index ddc290820d1..cda9ee670b0 100644 --- a/spec/models/project_services/slack_service/push_message_spec.rb +++ b/spec/models/project_services/slack_service/push_message_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe SlackService::PushMessage do +describe SlackService::PushMessage, models: true do subject { SlackService::PushMessage.new(args) } let(:args) do diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb index 97b60e19e40..a9e0afad90f 100644 --- a/spec/models/project_services/slack_service_spec.rb +++ b/spec/models/project_services/slack_service_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' -describe SlackService do +describe SlackService, models: true do describe "Associations" do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } diff --git a/spec/models/project_snippet_spec.rb b/spec/models/project_snippet_spec.rb index 3e8f106d27f..cc92eb0bd9f 100644 --- a/spec/models/project_snippet_spec.rb +++ b/spec/models/project_snippet_spec.rb @@ -17,7 +17,7 @@ require 'spec_helper' -describe ProjectSnippet do +describe ProjectSnippet, models: true do describe "Associations" do it { is_expected.to belong_to(:project) } end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index f93935ebe3b..6ddb0e2b8f7 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -28,11 +28,12 @@ # import_type :string(255) # import_source :string(255) # commit_count :integer default(0) +# import_error :text # require 'spec_helper' -describe Project do +describe Project, models: true do describe 'associations' do it { is_expected.to belong_to(:group) } it { is_expected.to belong_to(:namespace) } @@ -345,17 +346,6 @@ describe Project do expect(project1.star_count).to eq(0) expect(project2.star_count).to eq(0) end - - it 'is decremented when an upvoter account is deleted' do - user = create :user - project = create :project, :public - user.toggle_star(project) - project.reload - expect(project.star_count).to eq(1) - user.destroy - project.reload - expect(project.star_count).to eq(0) - end end describe :avatar_type do @@ -415,12 +405,15 @@ describe Project do it { expect(project.ci_commit(commit.sha)).to eq(commit) } end - describe :enable_ci do + describe :builds_enabled do let(:project) { create :project } - before { project.enable_ci } + before { project.builds_enabled = true } - it { expect(project.gitlab_ci?).to be_truthy } + subject { project.builds_enabled } + + it { is_expected.to eq(project.gitlab_ci_service.active) } + it { expect(project.builds_enabled?).to be_truthy } it { expect(project.gitlab_ci_project).to be_a(Ci::Project) } end @@ -452,7 +445,9 @@ describe Project do before do 2.times do - create(:note_on_commit, project: project2, created_at: date) + # Little fix for special issue related to Fractional Seconds support for MySQL. + # See: https://github.com/rails/rails/pull/14359/files + create(:note_on_commit, project: project2, created_at: date + 1) end end @@ -461,4 +456,23 @@ describe Project do end end end + + describe '.visible_to_user' do + let!(:project) { create(:project, :private) } + let!(:user) { create(:user) } + + subject { described_class.visible_to_user(user) } + + describe 'when a user has access to a project' do + before do + project.team.add_user(user, Gitlab::Access::MASTER) + end + + it { is_expected.to eq([project]) } + end + + describe 'when a user does not have access to any projects' do + it { is_expected.to eq([]) } + end + end end diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb index 26e8fdae472..5cd5ae327bf 100644 --- a/spec/models/project_team_spec.rb +++ b/spec/models/project_team_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe ProjectTeam do +describe ProjectTeam, models: true do let(:master) { create(:user) } let(:reporter) { create(:user) } let(:guest) { create(:user) } diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb index 9f6cdeeaa96..876b927eaea 100644 --- a/spec/models/project_wiki_spec.rb +++ b/spec/models/project_wiki_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe ProjectWiki do +describe ProjectWiki, models: true do let(:project) { create(:empty_project) } let(:repository) { project.repository } let(:user) { project.owner } @@ -184,6 +184,12 @@ describe ProjectWiki do subject.create_page("test page", "some content", :markdown, "commit message") expect(subject.pages.first.page.version.message).to eq("commit message") end + + it 'updates project activity' do + expect(subject).to receive(:update_project_activity) + + subject.create_page('Test Page', 'This is content') + end end describe "#update_page" do @@ -205,6 +211,12 @@ describe ProjectWiki do it "sets the correct commit message" do expect(@page.version.message).to eq("updated page") end + + it 'updates project activity' do + expect(subject).to receive(:update_project_activity) + + subject.update_page(@gollum_page, 'Yet more content', :markdown, 'Updated page again') + end end describe "#delete_page" do @@ -217,6 +229,12 @@ describe ProjectWiki do subject.delete_page(@page) expect(subject.pages.count).to eq(0) end + + it 'updates project activity' do + expect(subject).to receive(:update_project_activity) + + subject.delete_page(@page) + end end private diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb index 1e6937b536c..7e956cf6779 100644 --- a/spec/models/protected_branch_spec.rb +++ b/spec/models/protected_branch_spec.rb @@ -12,7 +12,7 @@ require 'spec_helper' -describe ProtectedBranch do +describe ProtectedBranch, models: true do describe 'Associations' do it { is_expected.to belong_to(:project) } end diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb index 527005b2b69..72ecb442a36 100644 --- a/spec/models/release_spec.rb +++ b/spec/models/release_spec.rb @@ -1,3 +1,15 @@ +# == Schema Information +# +# Table name: releases +# +# id :integer not null, primary key +# tag :string(255) +# description :text +# project_id :integer +# created_at :datetime +# updated_at :datetime +# + require 'rails_helper' RSpec.describe Release, type: :model do diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 49af0229bb5..afbf62035ac 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -1,9 +1,10 @@ require 'spec_helper' -describe Repository do +describe Repository, models: true do include RepoHelpers let(:repository) { create(:project).repository } + let(:user) { create(:user) } describe :branch_names_contains do subject { repository.branch_names_contains(sample_commit.id) } @@ -99,6 +100,7 @@ describe Repository do it { expect(subject.startline).to eq(186) } it { expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n") } end + end describe "#license" do @@ -120,4 +122,101 @@ describe Repository do expect(repository.license.name).to eq('licence') end end + + describe :add_branch do + context 'when pre hooks were successful' do + it 'should run without errors' do + hook = double(trigger: true) + expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook) + + expect { repository.add_branch(user, 'new_feature', 'master') }.not_to raise_error + end + + it 'should create the branch' do + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(true) + + branch = repository.add_branch(user, 'new_feature', 'master') + + expect(branch.name).to eq('new_feature') + end + end + + context 'when pre hooks failed' do + it 'should get an error' do + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(false) + + expect do + repository.add_branch(user, 'new_feature', 'master') + end.to raise_error(GitHooksService::PreReceiveError) + end + + it 'should not create the branch' do + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(false) + + expect do + repository.add_branch(user, 'new_feature', 'master') + end.to raise_error(GitHooksService::PreReceiveError) + expect(repository.find_branch('new_feature')).to be_nil + end + end + end + + describe :rm_branch do + context 'when pre hooks were successful' do + it 'should run without errors' do + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(true) + + expect { repository.rm_branch(user, 'feature') }.not_to raise_error + end + + it 'should delete the branch' do + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(true) + + expect { repository.rm_branch(user, 'feature') }.not_to raise_error + + expect(repository.find_branch('feature')).to be_nil + end + end + + context 'when pre hooks failed' do + it 'should get an error' do + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(false) + + expect do + repository.rm_branch(user, 'new_feature') + end.to raise_error(GitHooksService::PreReceiveError) + end + + it 'should not delete the branch' do + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(false) + + expect do + repository.rm_branch(user, 'feature') + end.to raise_error(GitHooksService::PreReceiveError) + expect(repository.find_branch('feature')).not_to be_nil + end + end + end + + describe :commit_with_hooks do + context 'when pre hooks were successful' do + it 'should run without errors' do + expect_any_instance_of(GitHooksService).to receive(:execute).and_return(true) + + expect do + repository.commit_with_hooks(user, 'feature') { sample_commit.id } + end.not_to raise_error + end + end + + context 'when pre hooks failed' do + it 'should get an error' do + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(false) + + expect do + repository.commit_with_hooks(user, 'feature') { sample_commit.id } + end.to raise_error(GitHooksService::PreReceiveError) + end + end + end end diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index 692e5fda3ba..0ca82365b98 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' -describe Service do +describe Service, models: true do describe "Associations" do it { is_expected.to belong_to :project } diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index 81581838675..eb2dbbdc5a4 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -17,7 +17,7 @@ require 'spec_helper' -describe Snippet do +describe Snippet, models: true do describe 'modules' do subject { described_class } diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 49e0bfdd2ec..daa9d1087bf 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -54,11 +54,14 @@ # public_email :string(255) default(""), not null # dashboard :integer default(0) # project_view :integer default(0) +# consumed_timestep :integer +# layout :integer default(0) +# hide_project_limit :boolean default(FALSE) # require 'spec_helper' -describe User do +describe User, models: true do include Gitlab::CurrentSettings describe 'modules' do @@ -89,7 +92,23 @@ describe User do end describe 'validations' do - it { is_expected.to validate_presence_of(:username) } + describe 'username' do + it 'validates presence' do + expect(subject).to validate_presence_of(:username) + end + + it 'rejects blacklisted names' do + user = build(:user, username: 'dashboard') + + expect(user).not_to be_valid + expect(user.errors.values).to eq [['dashboard is a reserved name']] + end + + it 'validates uniqueness' do + expect(subject).to validate_uniqueness_of(:username) + end + end + it { is_expected.to validate_presence_of(:projects_limit) } it { is_expected.to validate_numericality_of(:projects_limit) } it { is_expected.to allow_value(0).for(:projects_limit) } @@ -684,7 +703,7 @@ describe User do end end - describe "#contributed_projects_ids" do + describe "#contributed_projects" do subject { create(:user) } let!(:project1) { create(:project) } let!(:project2) { create(:project, forked_from_project: project3) } @@ -699,15 +718,15 @@ describe User do end it "includes IDs for projects the user has pushed to" do - expect(subject.contributed_projects_ids).to include(project1.id) + expect(subject.contributed_projects).to include(project1) end it "includes IDs for projects the user has had merge requests merged into" do - expect(subject.contributed_projects_ids).to include(project3.id) + expect(subject.contributed_projects).to include(project3) end it "doesn't include IDs for unrelated projects" do - expect(subject.contributed_projects_ids).not_to include(project2.id) + expect(subject.contributed_projects).not_to include(project2) end end @@ -756,4 +775,30 @@ describe User do expect(subject.recent_push).to eq(nil) end end + + describe '#authorized_groups' do + let!(:user) { create(:user) } + let!(:private_group) { create(:group) } + + before do + private_group.add_user(user, Gitlab::Access::MASTER) + end + + subject { user.authorized_groups } + + it { is_expected.to eq([private_group]) } + end + + describe '#authorized_projects' do + let!(:user) { create(:user) } + let!(:private_project) { create(:project, :private) } + + before do + private_project.team << [user, Gitlab::Access::MASTER] + end + + subject { user.authorized_projects } + + it { is_expected.to eq([private_project]) } + end end diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb index d7802d1734f..c1b03838aa9 100644 --- a/spec/models/wiki_page_spec.rb +++ b/spec/models/wiki_page_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe WikiPage do +describe WikiPage, models: true do let(:project) { create(:empty_project) } let(:user) { project.owner } let(:wiki) { ProjectWiki.new(project, user) } diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 13cced81875..4cfa49d1566 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -10,6 +10,8 @@ describe API::API, api: true do let(:avatar_file_path) { File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') } let!(:group1) { create(:group, avatar: File.open(avatar_file_path)) } let!(:group2) { create(:group) } + let!(:project1) { create(:project, namespace: group1) } + let!(:project2) { create(:project, namespace: group2) } before do group1.add_owner(user1) @@ -67,7 +69,7 @@ describe API::API, api: true do it "should return any existing group" do get api("/groups/#{group2.id}", admin) expect(response.status).to eq(200) - json_response['name'] == group2.name + expect(json_response['name']).to eq(group2.name) end it "should not return a non existing group" do @@ -80,7 +82,7 @@ describe API::API, api: true do it 'should return any existing group' do get api("/groups/#{group1.path}", admin) expect(response.status).to eq(200) - json_response['name'] == group2.name + expect(json_response['name']).to eq(group1.name) end it 'should not return a non existing group' do @@ -95,6 +97,59 @@ describe API::API, api: true do end end + describe "GET /groups/:id/projects" do + context "when authenticated as user" do + it "should return the group's projects" do + get api("/groups/#{group1.id}/projects", user1) + expect(response.status).to eq(200) + expect(json_response.length).to eq(1) + expect(json_response.first['name']).to eq(project1.name) + end + + it "should not return a non existing group" do + get api("/groups/1328/projects", user1) + expect(response.status).to eq(404) + end + + it "should not return a group not attached to user1" do + get api("/groups/#{group2.id}/projects", user1) + expect(response.status).to eq(403) + end + end + + context "when authenticated as admin" do + it "should return any existing group" do + get api("/groups/#{group2.id}/projects", admin) + expect(response.status).to eq(200) + expect(json_response.length).to eq(1) + expect(json_response.first['name']).to eq(project2.name) + end + + it "should not return a non existing group" do + get api("/groups/1328/projects", admin) + expect(response.status).to eq(404) + end + end + + context 'when using group path in URL' do + it 'should return any existing group' do + get api("/groups/#{group1.path}/projects", admin) + expect(response.status).to eq(200) + expect(json_response.first['name']).to eq(project1.name) + end + + it 'should not return a non existing group' do + get api('/groups/unknown/projects', admin) + expect(response.status).to eq(404) + end + + it 'should not return a group not attached to user1' do + get api("/groups/#{group2.path}/projects", user1) + expect(response.status).to eq(403) + end + end + end + describe "POST /groups" do context "when authenticated as user without group permissions" do it "should not create group" do diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb index aff109a9424..667f0dbea5c 100644 --- a/spec/requests/api/labels_spec.rb +++ b/spec/requests/api/labels_spec.rb @@ -47,7 +47,7 @@ describe API::API, api: true do name: 'Foo', color: '#FFAA' expect(response.status).to eq(400) - expect(json_response['message']['color']).to eq(['is invalid']) + expect(json_response['message']['color']).to eq(['must be a valid color code']) end it 'should return 400 for too long color code' do @@ -55,7 +55,7 @@ describe API::API, api: true do name: 'Foo', color: '#FFAAFFFF' expect(response.status).to eq(400) - expect(json_response['message']['color']).to eq(['is invalid']) + expect(json_response['message']['color']).to eq(['must be a valid color code']) end it 'should return 400 for invalid name' do @@ -151,12 +151,12 @@ describe API::API, api: true do expect(json_response['message']['title']).to eq(['is invalid']) end - it 'should return 400 for invalid name' do + it 'should return 400 when color code is too short' do put api("/projects/#{project.id}/labels", user), name: 'label1', color: '#FF' expect(response.status).to eq(400) - expect(json_response['message']['color']).to eq(['is invalid']) + expect(json_response['message']['color']).to eq(['must be a valid color code']) end it 'should return 400 for too long color code' do @@ -164,7 +164,7 @@ describe API::API, api: true do name: 'Foo', color: '#FFAAFFFF' expect(response.status).to eq(400) - expect(json_response['message']['color']).to eq(['is invalid']) + expect(json_response['message']['color']).to eq(['must be a valid color code']) end end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index a68c7b1e461..a91fa735321 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -131,6 +131,23 @@ describe API::API, api: true do end end + describe 'GET /projects/:id/merge_request/:merge_request_id/commits' do + context 'valid merge request' do + before { get api("/projects/#{project.id}/merge_request/#{merge_request.id}/commits", user) } + let(:commit) { merge_request.commits.first } + + it { expect(response.status).to eq 200 } + it { expect(json_response.size).to eq(merge_request.commits.size) } + it { expect(json_response.first['id']).to eq(commit.id) } + it { expect(json_response.first['title']).to eq(commit.title) } + end + + it 'returns a 404 when merge_request_id not found' do + get api("/projects/#{project.id}/merge_request/999/commits", user) + expect(response.status).to eq(404) + end + end + describe 'GET /projects/:id/merge_request/:merge_request_id/changes' do it 'should return the change information of the merge_request' do get api("/projects/#{project.id}/merge_request/#{merge_request.id}/changes", user) @@ -303,19 +320,21 @@ describe API::API, api: true do end describe "PUT /projects/:id/merge_request/:merge_request_id/merge" do + let(:ci_commit) { create(:ci_commit_without_jobs) } + it "should return merge_request in case of success" do put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user) expect(response.status).to eq(200) end - it "should return 405 if branch can't be merged" do + it "should return 406 if branch can't be merged" do allow_any_instance_of(MergeRequest). to receive(:can_be_merged?).and_return(false) put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user) - expect(response.status).to eq(405) + expect(response.status).to eq(406) expect(json_response['message']).to eq('Branch cannot be merged') end @@ -340,6 +359,17 @@ describe API::API, api: true do expect(response.status).to eq(401) expect(json_response['message']).to eq('401 Unauthorized') end + + it "enables merge when build succeeds if the ci is active" do + allow_any_instance_of(MergeRequest).to receive(:ci_commit).and_return(ci_commit) + allow(ci_commit).to receive(:active?).and_return(true) + + put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user), merge_when_build_succeeds: true + + expect(response.status).to eq(200) + expect(json_response['title']).to eq('Test') + expect(json_response['merge_when_build_succeeds']).to eq(true) + end end describe "PUT /projects/:id/merge_request/:merge_request_id" do diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index e9de9e0826d..24b765f4979 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -88,8 +88,11 @@ describe API::API, api: true do end it 'returns projects in the correct order when ci_enabled_first parameter is passed' do - [project, project2, project3].each{ |project| project.build_missing_services } - project2.gitlab_ci_service.update(active: true) + [project, project2, project3].each do |project| + project.builds_enabled = false + project.build_missing_services + end + project2.builds_enabled = true get api('/projects', user), { ci_enabled_first: 'true' } expect(response.status).to eq(200) expect(json_response).to be_an Array @@ -386,14 +389,30 @@ describe API::API, api: true do describe 'GET /projects/:id/events' do before { project_member2 } - it 'should return a project events' do - get api("/projects/#{project.id}/events", user) - expect(response.status).to eq(200) - json_event = json_response.first + context 'valid request' do + before do + note = create(:note_on_issue, note: 'What an awesome day!', project: project) + EventCreateService.new.leave_note(note, note.author) + get api("/projects/#{project.id}/events", user) + end + + it { expect(response.status).to eq(200) } + + context 'joined event' do + let(:json_event) { json_response[1] } + + it { expect(json_event['action_name']).to eq('joined') } + it { expect(json_event['project_id'].to_i).to eq(project.id) } + it { expect(json_event['author_username']).to eq(user3.username) } + it { expect(json_event['author']['name']).to eq(user3.name) } + end + + context 'comment event' do + let(:json_event) { json_response.first } - expect(json_event['action_name']).to eq('joined') - expect(json_event['project_id'].to_i).to eq(project.id) - expect(json_event['author_username']).to eq(user3.username) + it { expect(json_event['action_name']).to eq('commented on') } + it { expect(json_event['note']['body']).to eq('What an awesome day!') } + end end it 'should return a 404 error if not found' do @@ -723,6 +742,18 @@ describe API::API, api: true do end end + it 'should update visibility_level from public to private' do + project3.update_attributes({ visibility_level: Gitlab::VisibilityLevel::PUBLIC }) + + project_param = { public: false } + put api("/projects/#{project3.id}", user), project_param + expect(response.status).to eq(200) + project_param.each_pair do |k, v| + expect(json_response[k.to_s]).to eq(v) + end + expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE) + end + it 'should not update name to existing name' do project_param = { name: project3.name } put api("/projects/#{project.id}", user), project_param diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index faf6b77a462..4911cdd9da6 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -11,81 +11,6 @@ describe API::API, api: true do let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) } let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) } - describe "GET /projects/:id/repository/tags" do - it "should return an array of project tags" do - get api("/projects/#{project.id}/repository/tags", user) - expect(response.status).to eq(200) - expect(json_response).to be_an Array - expect(json_response.first['name']).to eq(project.repo.tags.sort_by(&:name).reverse.first.name) - end - end - - describe 'POST /projects/:id/repository/tags' do - context 'lightweight tags' do - it 'should create a new tag' do - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'v7.0.1', - ref: 'master' - - expect(response.status).to eq(201) - expect(json_response['name']).to eq('v7.0.1') - end - end - - context 'annotated tag' do - it 'should create a new annotated tag' do - # Identity must be set in .gitconfig to create annotated tag. - repo_path = project.repository.path_to_repo - system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.name #{user.name})) - system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.email #{user.email})) - - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'v7.1.0', - ref: 'master', - message: 'Release 7.1.0' - - expect(response.status).to eq(201) - expect(json_response['name']).to eq('v7.1.0') - expect(json_response['message']).to eq('Release 7.1.0') - end - end - - it 'should deny for user without push access' do - post api("/projects/#{project.id}/repository/tags", user2), - tag_name: 'v1.9.0', - ref: '621491c677087aa243f165eab467bfdfbee00be1' - expect(response.status).to eq(403) - end - - it 'should return 400 if tag name is invalid' do - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'v 1.0.0', - ref: 'master' - expect(response.status).to eq(400) - expect(json_response['message']).to eq('Tag name invalid') - end - - it 'should return 400 if tag already exists' do - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'v8.0.0', - ref: 'master' - expect(response.status).to eq(201) - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'v8.0.0', - ref: 'master' - expect(response.status).to eq(400) - expect(json_response['message']).to eq('Tag already exists') - end - - it 'should return 400 if ref name is invalid' do - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'mytag', - ref: 'foo' - expect(response.status).to eq(400) - expect(json_response['message']).to eq('Invalid reference name') - end - end - describe "GET /projects/:id/repository/tree" do context "authorized user" do before { project.team << [user2, :reporter] } diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index c0226605a23..b180d2fec77 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -46,6 +46,7 @@ describe API::API, api: true do delete api("/projects/#{project.id}/services/#{dashed_service}", user) expect(response.status).to eq(200) + project.send(service_method).reload expect(project.send(service_method).activated?).to be_falsey end end diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb new file mode 100644 index 00000000000..17f2643fd45 --- /dev/null +++ b/spec/requests/api/tags_spec.rb @@ -0,0 +1,196 @@ +require 'spec_helper' +require 'mime/types' + +describe API::API, api: true do + include ApiHelpers + include RepoHelpers + + let(:user) { create(:user) } + let(:user2) { create(:user) } + let!(:project) { create(:project, creator_id: user.id) } + let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) } + let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) } + + describe "GET /projects/:id/repository/tags" do + let(:tag_name) { project.repository.tag_names.sort.reverse.first } + let(:description) { 'Awesome release!' } + + context 'without releases' do + it "should return an array of project tags" do + get api("/projects/#{project.id}/repository/tags", user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.first['name']).to eq(tag_name) + end + end + + context 'with releases' do + before do + release = project.releases.find_or_initialize_by(tag: tag_name) + release.update_attributes(description: description) + end + + it "should return an array of project tags with release info" do + get api("/projects/#{project.id}/repository/tags", user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.first['name']).to eq(tag_name) + expect(json_response.first['release']['description']).to eq(description) + end + end + end + + describe 'POST /projects/:id/repository/tags' do + context 'lightweight tags' do + it 'should create a new tag' do + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v7.0.1', + ref: 'master' + + expect(response.status).to eq(201) + expect(json_response['name']).to eq('v7.0.1') + end + end + + context 'lightweight tags with release notes' do + it 'should create a new tag' do + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v7.0.1', + ref: 'master', + release_description: 'Wow' + + expect(response.status).to eq(201) + expect(json_response['name']).to eq('v7.0.1') + expect(json_response['release']['description']).to eq('Wow') + end + end + + context 'annotated tag' do + it 'should create a new annotated tag' do + # Identity must be set in .gitconfig to create annotated tag. + repo_path = project.repository.path_to_repo + system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.name #{user.name})) + system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.email #{user.email})) + + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v7.1.0', + ref: 'master', + message: 'Release 7.1.0' + + expect(response.status).to eq(201) + expect(json_response['name']).to eq('v7.1.0') + expect(json_response['message']).to eq('Release 7.1.0') + end + end + + it 'should deny for user without push access' do + post api("/projects/#{project.id}/repository/tags", user2), + tag_name: 'v1.9.0', + ref: '621491c677087aa243f165eab467bfdfbee00be1' + expect(response.status).to eq(403) + end + + it 'should return 400 if tag name is invalid' do + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v 1.0.0', + ref: 'master' + expect(response.status).to eq(400) + expect(json_response['message']).to eq('Tag name invalid') + end + + it 'should return 400 if tag already exists' do + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v8.0.0', + ref: 'master' + expect(response.status).to eq(201) + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v8.0.0', + ref: 'master' + expect(response.status).to eq(400) + expect(json_response['message']).to eq('Tag already exists') + end + + it 'should return 400 if ref name is invalid' do + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'mytag', + ref: 'foo' + expect(response.status).to eq(400) + expect(json_response['message']).to eq('Invalid reference name') + end + end + + describe 'POST /projects/:id/repository/tags/:tag_name/release' do + let(:tag_name) { project.repository.tag_names.first } + let(:description) { 'Awesome release!' } + + it 'should create description for existing git tag' do + post api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user), + description: description + + expect(response.status).to eq(201) + expect(json_response['tag_name']).to eq(tag_name) + expect(json_response['description']).to eq(description) + end + + it 'should return 404 if the tag does not exist' do + post api("/projects/#{project.id}/repository/tags/foobar/release", user), + description: description + + expect(response.status).to eq(404) + expect(json_response['message']).to eq('Tag does not exist') + end + + context 'on tag with existing release' do + before do + release = project.releases.find_or_initialize_by(tag: tag_name) + release.update_attributes(description: description) + end + + it 'should return 409 if there is already a release' do + post api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user), + description: description + + expect(response.status).to eq(409) + expect(json_response['message']).to eq('Release already exists') + end + end + end + + describe 'PUT id/repository/tags/:tag_name/release' do + let(:tag_name) { project.repository.tag_names.first } + let(:description) { 'Awesome release!' } + let(:new_description) { 'The best release!' } + + context 'on tag with existing release' do + before do + release = project.releases.find_or_initialize_by(tag: tag_name) + release.update_attributes(description: description) + end + + it 'should update the release description' do + put api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user), + description: new_description + + expect(response.status).to eq(200) + expect(json_response['tag_name']).to eq(tag_name) + expect(json_response['description']).to eq(new_description) + end + end + + it 'should return 404 if the tag does not exist' do + put api("/projects/#{project.id}/repository/tags/foobar/release", user), + description: new_description + + expect(response.status).to eq(404) + expect(json_response['message']).to eq('Tag does not exist') + end + + it 'should return 404 if the release does not exist' do + put api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user), + description: new_description + + expect(response.status).to eq(404) + expect(json_response['message']).to eq('Release does not exist') + end + end +end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index d26a300ed82..2f609c63330 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -153,7 +153,7 @@ describe API::API, api: true do expect(json_response['message']['projects_limit']). to eq(['must be greater than or equal to 0']) expect(json_response['message']['username']). - to eq([Gitlab::Regex.send(:namespace_regex_message)]) + to eq([Gitlab::Regex.namespace_regex_message]) end it "shouldn't available for non admin users" do @@ -296,7 +296,7 @@ describe API::API, api: true do expect(json_response['message']['projects_limit']). to eq(['must be greater than or equal to 0']) expect(json_response['message']['username']). - to eq([Gitlab::Regex.send(:namespace_regex_message)]) + to eq([Gitlab::Regex.namespace_regex_message]) end context "with existing user" do @@ -343,8 +343,9 @@ describe API::API, api: true do end.to change{ user.keys.count }.by(1) end - it "should raise error for invalid ID" do - expect{post api("/users/ASDF/keys", admin) }.to raise_error(ActionController::RoutingError) + it "should return 405 for invalid ID" do + post api("/users/ASDF/keys", admin) + expect(response.status).to eq(405) end end @@ -374,9 +375,9 @@ describe API::API, api: true do expect(json_response.first['title']).to eq(key.title) end - it "should return 404 for invalid ID" do + it "should return 405 for invalid ID" do get api("/users/ASDF/keys", admin) - expect(response.status).to eq(404) + expect(response.status).to eq(405) end end end @@ -434,7 +435,8 @@ describe API::API, api: true do end it "should raise error for invalid ID" do - expect{post api("/users/ASDF/emails", admin) }.to raise_error(ActionController::RoutingError) + post api("/users/ASDF/emails", admin) + expect(response.status).to eq(405) end end @@ -465,7 +467,8 @@ describe API::API, api: true do end it "should raise error for invalid ID" do - expect{put api("/users/ASDF/emails", admin) }.to raise_error(ActionController::RoutingError) + put api("/users/ASDF/emails", admin) + expect(response.status).to eq(405) end end end diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 88218a93e1f..c2be045099d 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -5,7 +5,7 @@ describe Ci::API::API do let(:runner) { FactoryGirl.create(:ci_runner, tag_list: ["mysql", "ruby"]) } let(:project) { FactoryGirl.create(:ci_project) } - let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) } + let(:gl_project) { project.gl_project } before do stub_ci_commit_to_return_yaml_file @@ -14,7 +14,7 @@ describe Ci::API::API do describe "Builds API for runners" do let(:shared_runner) { FactoryGirl.create(:ci_runner, token: "SharedRunner") } let(:shared_project) { FactoryGirl.create(:ci_project, name: "SharedProject") } - let(:shared_gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: shared_project) } + let(:shared_gl_project) { shared_project.gl_project } before do FactoryGirl.create :ci_runner_project, project_id: project.id, runner_id: runner.id @@ -41,7 +41,7 @@ describe Ci::API::API do it "should return 404 error if no builds for specific runner" do commit = FactoryGirl.create(:ci_commit, gl_project: shared_gl_project) - FactoryGirl.create(:ci_build, commit: commit, status: 'pending' ) + FactoryGirl.create(:ci_build, commit: commit, status: 'pending') post ci_api("/builds/register"), token: runner.token @@ -50,7 +50,7 @@ describe Ci::API::API do it "should return 404 error if no builds for shared runner" do commit = FactoryGirl.create(:ci_commit, gl_project: gl_project) - FactoryGirl.create(:ci_build, commit: commit, status: 'pending' ) + FactoryGirl.create(:ci_build, commit: commit, status: 'pending') post ci_api("/builds/register"), token: shared_runner.token @@ -79,7 +79,7 @@ describe Ci::API::API do { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true }, { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true }, { "key" => "DB_NAME", "value" => "postgres", "public" => true }, - { "key" => "SECRET_KEY", "value" => "secret_value", "public" => false }, + { "key" => "SECRET_KEY", "value" => "secret_value", "public" => false } ]) end @@ -122,5 +122,191 @@ describe Ci::API::API do expect(build.reload.trace).to eq 'hello_world' end end + + context "Artifacts" do + let(:file_upload) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') } + let(:file_upload2) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/gif') } + let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) } + let(:build) { FactoryGirl.create(:ci_build, commit: commit, runner_id: runner.id) } + let(:authorize_url) { ci_api("/builds/#{build.id}/artifacts/authorize") } + let(:post_url) { ci_api("/builds/#{build.id}/artifacts") } + let(:delete_url) { ci_api("/builds/#{build.id}/artifacts") } + let(:get_url) { ci_api("/builds/#{build.id}/artifacts") } + let(:headers) { { "GitLab-Workhorse" => "1.0" } } + let(:headers_with_token) { headers.merge(Ci::API::Helpers::BUILD_TOKEN_HEADER => build.project.token) } + + describe "POST /builds/:id/artifacts/authorize" do + context "should authorize posting artifact to running build" do + before do + build.run! + end + + it "using token as parameter" do + post authorize_url, { token: build.project.token }, headers + expect(response.status).to eq(200) + expect(json_response["TempPath"]).to_not be_nil + end + + it "using token as header" do + post authorize_url, {}, headers_with_token + expect(response.status).to eq(200) + expect(json_response["TempPath"]).to_not be_nil + end + end + + context "should fail to post too large artifact" do + before do + build.run! + end + + it "using token as parameter" do + stub_application_setting(max_artifacts_size: 0) + post authorize_url, { token: build.project.token, filesize: 100 }, headers + expect(response.status).to eq(413) + end + + it "using token as header" do + stub_application_setting(max_artifacts_size: 0) + post authorize_url, { filesize: 100 }, headers_with_token + expect(response.status).to eq(413) + end + end + + context "should get denied" do + it do + post authorize_url, { token: 'invalid', filesize: 100 } + expect(response.status).to eq(403) + end + end + end + + describe "POST /builds/:id/artifacts" do + context "Disable sanitizer" do + before do + # by configuring this path we allow to pass temp file from any path + allow(ArtifactUploader).to receive(:artifacts_upload_path).and_return('/') + end + + context "should post artifact to running build" do + before do + build.run! + end + + it "uses regual file post" do + upload_artifacts(file_upload, headers_with_token, false) + expect(response.status).to eq(201) + expect(json_response["artifacts_file"]["filename"]).to eq(file_upload.original_filename) + end + + it "uses accelerated file post" do + upload_artifacts(file_upload, headers_with_token, true) + expect(response.status).to eq(201) + expect(json_response["artifacts_file"]["filename"]).to eq(file_upload.original_filename) + end + + it "updates artifact" do + upload_artifacts(file_upload, headers_with_token) + upload_artifacts(file_upload2, headers_with_token) + expect(response.status).to eq(201) + expect(json_response["artifacts_file"]["filename"]).to eq(file_upload2.original_filename) + end + end + + context "should fail to post too large artifact" do + before do + build.run! + end + + it do + stub_application_setting(max_artifacts_size: 0) + upload_artifacts(file_upload, headers_with_token) + expect(response.status).to eq(413) + end + end + + context "should fail to post artifacts without file" do + before do + build.run! + end + + it do + post post_url, {}, headers_with_token + expect(response.status).to eq(400) + end + end + + context "should fail to post artifacts without GitLab-Workhorse" do + before do + build.run! + end + + it do + post post_url, { token: build.project.token }, {} + expect(response.status).to eq(403) + end + end + end + + context "should fail to post artifacts for outside of tmp path" do + before do + # by configuring this path we allow to pass file from @tmpdir only + # but all temporary files are stored in system tmp directory + @tmpdir = Dir.mktmpdir + allow(ArtifactUploader).to receive(:artifacts_upload_path).and_return(@tmpdir) + build.run! + end + + after do + FileUtils.remove_entry @tmpdir + end + + it do + upload_artifacts(file_upload, headers_with_token) + expect(response.status).to eq(400) + end + end + + def upload_artifacts(file, headers = {}, accelerated = true) + if accelerated + post post_url, { + 'file.path' => file.path, + 'file.name' => file.original_filename + }, headers + else + post post_url, { file: file }, headers + end + end + end + + describe "DELETE /builds/:id/artifacts" do + before do + build.run! + post delete_url, token: build.project.token, file: file_upload + end + + it "should delete artifact build" do + build.success + delete delete_url, token: build.project.token + expect(response.status).to eq(200) + end + end + + describe "GET /builds/:id/artifacts" do + before do + build.run! + end + + it "should download artifact" do + build.update_attributes(artifacts_file: file_upload) + get get_url, token: build.project.token + expect(response.status).to eq(200) + end + + it "should fail to download if no artifact uploaded" do + get get_url, token: build.project.token + expect(response.status).to eq(404) + end + end + end end end diff --git a/spec/requests/ci/api/commits_spec.rb b/spec/requests/ci/api/commits_spec.rb index 6049135fd10..aa51ba95bca 100644 --- a/spec/requests/ci/api/commits_spec.rb +++ b/spec/requests/ci/api/commits_spec.rb @@ -4,7 +4,7 @@ describe Ci::API::API, 'Commits' do include ApiHelpers let(:project) { FactoryGirl.create(:ci_project) } - let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) } + let(:gl_project) { project.gl_project } let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) } let(:options) do diff --git a/spec/requests/ci/api/projects_spec.rb b/spec/requests/ci/api/projects_spec.rb index 53f7f91cc1f..893fd168d3e 100644 --- a/spec/requests/ci/api/projects_spec.rb +++ b/spec/requests/ci/api/projects_spec.rb @@ -41,8 +41,8 @@ describe Ci::API::API do describe "GET /projects/owned" do let!(:gl_project1) {FactoryGirl.create(:empty_project, namespace: user.namespace)} let!(:gl_project2) {FactoryGirl.create(:empty_project, namespace: user.namespace)} - let!(:project1) { FactoryGirl.create(:ci_project, gl_project: gl_project1) } - let!(:project2) { FactoryGirl.create(:ci_project, gl_project: gl_project2) } + let!(:project1) { gl_project1.ensure_gitlab_ci_project } + let!(:project2) { gl_project2.ensure_gitlab_ci_project } before do project1.gl_project.team << [user, :developer] @@ -180,87 +180,53 @@ describe Ci::API::API do end end - describe "POST /projects" do - let(:gl_project) { FactoryGirl.create :empty_project } - let(:project_info) do - { - gitlab_id: gl_project.id - } - end - - let(:invalid_project_info) { {} } + describe "POST /projects/:id/runners/:id" do + let(:project) { FactoryGirl.create(:ci_project) } + let(:runner) { FactoryGirl.create(:ci_runner) } - context "with valid project info" do - before do - options.merge!(project_info) - end + it "should add the project to the runner" do + project.gl_project.team << [user, :master] + post ci_api("/projects/#{project.id}/runners/#{runner.id}"), options + expect(response.status).to eq(201) - it "should create a project with valid data" do - post ci_api("/projects"), options - expect(response.status).to eq(201) - expect(json_response['name']).to eq(gl_project.name_with_namespace) - end + project.reload + expect(project.runners.first.id).to eq(runner.id) end - context "with invalid project info" do - before do - options.merge!(invalid_project_info) - end + it "should fail if it tries to link a non-existing project or runner" do + post ci_api("/projects/#{project.id}/runners/non-existing"), options + expect(response.status).to eq(404) - it "should error with invalid data" do - post ci_api("/projects"), options - expect(response.status).to eq(400) - end + post ci_api("/projects/non-existing/runners/#{runner.id}"), options + expect(response.status).to eq(404) end - describe "POST /projects/:id/runners/:id" do - let(:project) { FactoryGirl.create(:ci_project) } - let(:runner) { FactoryGirl.create(:ci_runner) } - - it "should add the project to the runner" do - project.gl_project.team << [user, :master] - post ci_api("/projects/#{project.id}/runners/#{runner.id}"), options - expect(response.status).to eq(201) - - project.reload - expect(project.runners.first.id).to eq(runner.id) - end - - it "should fail if it tries to link a non-existing project or runner" do - post ci_api("/projects/#{project.id}/runners/non-existing"), options - expect(response.status).to eq(404) - - post ci_api("/projects/non-existing/runners/#{runner.id}"), options - expect(response.status).to eq(404) - end - - it "non-manager is not authorized" do - allow_any_instance_of(User).to receive(:can_manage_project?).and_return(false) - post ci_api("/projects/#{project.id}/runners/#{runner.id}"), options - expect(response.status).to eq(401) - end + it "non-manager is not authorized" do + allow_any_instance_of(User).to receive(:can_manage_project?).and_return(false) + post ci_api("/projects/#{project.id}/runners/#{runner.id}"), options + expect(response.status).to eq(401) end + end - describe "DELETE /projects/:id/runners/:id" do - let(:project) { FactoryGirl.create(:ci_project) } - let(:runner) { FactoryGirl.create(:ci_runner) } + describe "DELETE /projects/:id/runners/:id" do + let(:project) { FactoryGirl.create(:ci_project) } + let(:runner) { FactoryGirl.create(:ci_runner) } - it "should remove the project from the runner" do - project.gl_project.team << [user, :master] - post ci_api("/projects/#{project.id}/runners/#{runner.id}"), options + it "should remove the project from the runner" do + project.gl_project.team << [user, :master] + post ci_api("/projects/#{project.id}/runners/#{runner.id}"), options - expect(project.runners).to be_present - delete ci_api("/projects/#{project.id}/runners/#{runner.id}"), options - expect(response.status).to eq(200) + expect(project.runners).to be_present + delete ci_api("/projects/#{project.id}/runners/#{runner.id}"), options + expect(response.status).to eq(200) - project.reload - expect(project.runners).to be_empty - end + project.reload + expect(project.runners).to be_empty + end - it "non-manager is not authorized" do - delete ci_api("/projects/#{project.id}/runners/#{runner.id}"), options - expect(response.status).to eq(401) - end + it "non-manager is not authorized" do + delete ci_api("/projects/#{project.id}/runners/#{runner.id}"), options + expect(response.status).to eq(401) end end end diff --git a/spec/requests/ci/api/triggers_spec.rb b/spec/requests/ci/api/triggers_spec.rb index 93617fc4b3f..a2b436d5811 100644 --- a/spec/requests/ci/api/triggers_spec.rb +++ b/spec/requests/ci/api/triggers_spec.rb @@ -6,7 +6,7 @@ describe Ci::API::API do describe 'POST /projects/:project_id/refs/:ref/trigger' do let!(:trigger_token) { 'secure token' } let!(:gl_project) { FactoryGirl.create(:project) } - let!(:project) { FactoryGirl.create(:ci_project, gl_project: gl_project) } + let!(:project) { gl_project.ensure_gitlab_ci_project } let!(:project2) { FactoryGirl.create(:ci_project) } let!(:trigger) { FactoryGirl.create(:ci_trigger, project: project, token: trigger_token) } let(:options) do diff --git a/spec/services/archive_repository_service_spec.rb b/spec/services/archive_repository_service_spec.rb index f7a36cd9670..bd871605c66 100644 --- a/spec/services/archive_repository_service_spec.rb +++ b/spec/services/archive_repository_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe ArchiveRepositoryService do +describe ArchiveRepositoryService, services: true do let(:project) { create(:project) } subject { ArchiveRepositoryService.new(project, "master", "zip") } diff --git a/spec/services/ci/create_commit_service_spec.rb b/spec/services/ci/create_commit_service_spec.rb index e3a8fe9681b..c2fafca2ad2 100644 --- a/spec/services/ci/create_commit_service_spec.rb +++ b/spec/services/ci/create_commit_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' module Ci - describe CreateCommitService do + describe CreateCommitService, services: true do let(:service) { CreateCommitService.new } let(:project) { FactoryGirl.create(:ci_project) } let(:user) { nil } @@ -53,7 +53,7 @@ module Ci end end - it 'fails commits without .gitlab-ci.yml' do + it 'skips commits without .gitlab-ci.yml' do stub_ci_commit_yaml_file(nil) result = service.execute(project, user, ref: 'refs/heads/0_1', @@ -63,7 +63,24 @@ module Ci ) expect(result).to be_persisted expect(result.builds.any?).to be_falsey - expect(result.status).to eq('failed') + expect(result.status).to eq('skipped') + expect(result.yaml_errors).to be_nil + end + + it 'skips commits if yaml is invalid' do + message = 'message' + allow_any_instance_of(Ci::Commit).to receive(:git_commit_message) { message } + stub_ci_commit_yaml_file('invalid: file: file') + commits = [{ message: message }] + commit = service.execute(project, user, + ref: 'refs/tags/0_1', + before: '00000000', + after: '31das312', + commits: commits + ) + expect(commit.builds.any?).to be false + expect(commit.status).to eq('failed') + expect(commit.yaml_errors).to_not be_nil end describe :ci_skip? do @@ -100,7 +117,7 @@ module Ci end it "skips builds creation if there is [ci skip] tag in commit message and yaml is invalid" do - stub_ci_commit_yaml_file('invalid: file') + stub_ci_commit_yaml_file('invalid: file: fiile') commits = [{ message: message }] commit = service.execute(project, user, ref: 'refs/tags/0_1', @@ -110,6 +127,7 @@ module Ci ) expect(commit.builds.any?).to be false expect(commit.status).to eq("skipped") + expect(commit.yaml_errors).to be_nil end end diff --git a/spec/services/ci/create_trigger_request_service_spec.rb b/spec/services/ci/create_trigger_request_service_spec.rb index fcafae38644..c80cb58163a 100644 --- a/spec/services/ci/create_trigger_request_service_spec.rb +++ b/spec/services/ci/create_trigger_request_service_spec.rb @@ -1,9 +1,9 @@ require 'spec_helper' -describe Ci::CreateTriggerRequestService do +describe Ci::CreateTriggerRequestService, services: true do let(:service) { Ci::CreateTriggerRequestService.new } let(:gl_project) { create(:project) } - let(:project) { create(:ci_project, gl_project: gl_project) } + let(:project) { gl_project.ensure_gitlab_ci_project } let(:trigger) { create(:ci_trigger, project: project) } before do diff --git a/spec/services/ci/event_service_spec.rb b/spec/services/ci/event_service_spec.rb index 1264e17ff5e..32516c75cf3 100644 --- a/spec/services/ci/event_service_spec.rb +++ b/spec/services/ci/event_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Ci::EventService do +describe Ci::EventService, services: true do let(:project) { FactoryGirl.create :ci_project } let(:user) { double(username: "root", id: 1) } diff --git a/spec/services/ci/image_for_build_service_spec.rb b/spec/services/ci/image_for_build_service_spec.rb index cda7d0c4a51..b43cabb4ee4 100644 --- a/spec/services/ci/image_for_build_service_spec.rb +++ b/spec/services/ci/image_for_build_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' module Ci - describe ImageForBuildService do + describe ImageForBuildService, services: true do let(:service) { ImageForBuildService.new } let(:project) { FactoryGirl.create(:ci_project) } let(:gl_project) { FactoryGirl.create(:project, gitlab_ci_project: project) } diff --git a/spec/services/ci/register_build_service_spec.rb b/spec/services/ci/register_build_service_spec.rb index b370dfbe113..379e07982fb 100644 --- a/spec/services/ci/register_build_service_spec.rb +++ b/spec/services/ci/register_build_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' module Ci - describe RegisterBuildService do + describe RegisterBuildService, services: true do let!(:service) { RegisterBuildService.new } let!(:gl_project) { FactoryGirl.create :empty_project } let!(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project } diff --git a/spec/services/ci/web_hook_service_spec.rb b/spec/services/ci/web_hook_service_spec.rb index aa48fcbcbfd..e7d8ab30652 100644 --- a/spec/services/ci/web_hook_service_spec.rb +++ b/spec/services/ci/web_hook_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Ci::WebHookService do +describe Ci::WebHookService, services: true do let(:project) { FactoryGirl.create :ci_project } let(:gl_project) { FactoryGirl.create :empty_project, gitlab_ci_project: project } let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project } diff --git a/spec/services/create_release_service_spec.rb b/spec/services/create_release_service_spec.rb new file mode 100644 index 00000000000..61e5ae72f51 --- /dev/null +++ b/spec/services/create_release_service_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe CreateReleaseService, services: true do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:tag_name) { project.repository.tag_names.first } + let(:description) { 'Awesome release!' } + let(:service) { CreateReleaseService.new(project, user) } + + it 'creates a new release' do + result = service.execute(tag_name, description) + expect(result[:status]).to eq(:success) + release = project.releases.find_by(tag: tag_name) + expect(release).not_to be_nil + expect(release.description).to eq(description) + end + + it 'raises an error if the tag does not exist' do + result = service.execute("foobar", description) + expect(result[:status]).to eq(:error) + end + + context 'there already exists a release on a tag' do + before do + service.execute(tag_name, description) + end + + it 'raises an error and does not update the release' do + result = service.execute(tag_name, 'The best release!') + expect(result[:status]).to eq(:error) + expect(project.releases.find_by(tag: tag_name).description).to eq(description) + end + end +end diff --git a/spec/services/create_snippet_service_spec.rb b/spec/services/create_snippet_service_spec.rb index 8edabe9450b..c800dea04fa 100644 --- a/spec/services/create_snippet_service_spec.rb +++ b/spec/services/create_snippet_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe CreateSnippetService do +describe CreateSnippetService, services: true do before do @user = create :user @admin = create :user, admin: true diff --git a/spec/services/destroy_group_service_spec.rb b/spec/services/destroy_group_service_spec.rb index e28564b3866..afa89b84175 100644 --- a/spec/services/destroy_group_service_spec.rb +++ b/spec/services/destroy_group_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe DestroyGroupService do +describe DestroyGroupService, services: true do let!(:user) { create(:user) } let!(:group) { create(:group) } let!(:project) { create(:project, namespace: group) } diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb index 7756b973ecd..f6dc9d4008f 100644 --- a/spec/services/event_create_service_spec.rb +++ b/spec/services/event_create_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe EventCreateService do +describe EventCreateService, services: true do let(:service) { EventCreateService.new } describe 'Issues' do diff --git a/spec/services/git_hooks_service_spec.rb b/spec/services/git_hooks_service_spec.rb new file mode 100644 index 00000000000..2bb9c3b3db3 --- /dev/null +++ b/spec/services/git_hooks_service_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +describe GitHooksService, services: true do + include RepoHelpers + + let(:user) { create :user } + let(:project) { create :project } + let(:service) { GitHooksService.new } + + before do + @blankrev = Gitlab::Git::BLANK_SHA + @oldrev = sample_commit.parent_id + @newrev = sample_commit.id + @ref = 'refs/heads/feature' + @repo_path = project.repository.path_to_repo + end + + describe '#execute' do + + context 'when receive hooks were successful' do + it 'should call post-receive hook' do + hook = double(trigger: true) + expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook) + + expect(service.execute(user, @repo_path, @blankrev, @newrev, @ref) { }).to eq(true) + end + end + + context 'when pre-receive hook failed' do + it 'should not call post-receive hook' do + expect(service).to receive(:run_hook).with('pre-receive').and_return(false) + expect(service).not_to receive(:run_hook).with('post-receive') + + expect do + service.execute(user, @repo_path, @blankrev, @newrev, @ref) + end.to raise_error(GitHooksService::PreReceiveError) + end + end + + context 'when update hook failed' do + it 'should not call post-receive hook' do + expect(service).to receive(:run_hook).with('pre-receive').and_return(true) + expect(service).to receive(:run_hook).with('update').and_return(false) + expect(service).not_to receive(:run_hook).with('post-receive') + + expect do + service.execute(user, @repo_path, @blankrev, @newrev, @ref) + end.to raise_error(GitHooksService::PreReceiveError) + end + end + + end +end diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index 17015d29e51..a04c242cf0e 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe GitPushService do +describe GitPushService, services: true do include RepoHelpers let(:user) { create :user } diff --git a/spec/services/git_tag_push_service_spec.rb b/spec/services/git_tag_push_service_spec.rb index eed50c7ebac..e2d15f1a83d 100644 --- a/spec/services/git_tag_push_service_spec.rb +++ b/spec/services/git_tag_push_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe GitTagPushService do +describe GitTagPushService, services: true do include RepoHelpers let(:user) { create :user } diff --git a/spec/services/issues/bulk_update_service_spec.rb b/spec/services/issues/bulk_update_service_spec.rb index 4c62fbafd73..6a7ea4b2f44 100644 --- a/spec/services/issues/bulk_update_service_spec.rb +++ b/spec/services/issues/bulk_update_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Issues::BulkUpdateService do +describe Issues::BulkUpdateService, services: true do let(:issue) { create(:issue, project: @project) } before do diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb index db547ce0d50..3a8daf28f5e 100644 --- a/spec/services/issues/close_service_spec.rb +++ b/spec/services/issues/close_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Issues::CloseService do +describe Issues::CloseService, services: true do let(:user) { create(:user) } let(:user2) { create(:user) } let(:issue) { create(:issue, assignee: user2) } @@ -14,7 +14,9 @@ describe Issues::CloseService do describe :execute do context "valid params" do before do - @issue = Issues::CloseService.new(project, user, {}).execute(issue) + perform_enqueued_jobs do + @issue = Issues::CloseService.new(project, user, {}).execute(issue) + end end it { expect(@issue).to be_valid } diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb index 7f1ebcb3198..2148d091a57 100644 --- a/spec/services/issues/create_service_spec.rb +++ b/spec/services/issues/create_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Issues::CreateService do +describe Issues::CreateService, services: true do let(:project) { create(:empty_project) } let(:user) { create(:user) } diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index a91be3b4472..87da0e9618b 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -1,18 +1,31 @@ require 'spec_helper' -describe Issues::UpdateService do +describe Issues::UpdateService, services: true do let(:user) { create(:user) } let(:user2) { create(:user) } - let(:issue) { create(:issue, title: 'Old title') } + let(:user3) { create(:user) } + let(:issue) { create(:issue, title: 'Old title', assignee_id: user3.id) } let(:label) { create(:label) } let(:project) { issue.project } before do project.team << [user, :master] project.team << [user2, :developer] + project.team << [user3, :developer] end describe 'execute' do + def find_note(starting_with) + @issue.notes.find do |note| + note && note.note.start_with?(starting_with) + end + end + + def update_issue(opts) + @issue = Issues::UpdateService.new(project, user, opts).execute(issue) + @issue.reload + end + context "valid params" do before do opts = { @@ -23,7 +36,10 @@ describe Issues::UpdateService do label_ids: [label.id] } - @issue = Issues::UpdateService.new(project, user, opts).execute(issue) + perform_enqueued_jobs do + @issue = Issues::UpdateService.new(project, user, opts).execute(issue) + end + @issue.reload end @@ -34,18 +50,14 @@ describe Issues::UpdateService do it { expect(@issue.labels.count).to eq(1) } it { expect(@issue.labels.first.title).to eq('Bug') } - it 'should send email to user2 about assign of new issue' do - email = ActionMailer::Base.deliveries.last - expect(email.to.first).to eq(user2.email) + it 'should send email to user2 about assign of new issue and email to user3 about issue unassignment' do + deliveries = ActionMailer::Base.deliveries + email = deliveries.last + recipients = deliveries.last(2).map(&:to).flatten + expect(recipients).to include(user2.email, user3.email) expect(email.subject).to include(issue.title) end - def find_note(starting_with) - @issue.notes.find do |note| - note && note.note.start_with?(starting_with) - end - end - it 'should create system note about issue reassign' do note = find_note('Reassigned to') @@ -67,5 +79,71 @@ describe Issues::UpdateService do expect(note.note).to eq 'Title changed from **Old title** to **New title**' end end + + context 'when Issue has tasks' do + before { update_issue({ description: "- [ ] Task 1\n- [ ] Task 2" }) } + + it { expect(@issue.tasks?).to eq(true) } + + context 'when tasks are marked as completed' do + before { update_issue({ description: "- [x] Task 1\n- [X] Task 2" }) } + + it 'creates system note about task status change' do + note1 = find_note('Marked the task **Task 1** as completed') + note2 = find_note('Marked the task **Task 2** as completed') + + expect(note1).not_to be_nil + expect(note2).not_to be_nil + end + end + + context 'when tasks are marked as incomplete' do + before do + update_issue({ description: "- [x] Task 1\n- [X] Task 2" }) + update_issue({ description: "- [ ] Task 1\n- [ ] Task 2" }) + end + + it 'creates system note about task status change' do + note1 = find_note('Marked the task **Task 1** as incomplete') + note2 = find_note('Marked the task **Task 2** as incomplete') + + expect(note1).not_to be_nil + expect(note2).not_to be_nil + end + end + + context 'when tasks position has been modified' do + before do + update_issue({ description: "- [x] Task 1\n- [X] Task 2" }) + update_issue({ description: "- [x] Task 1\n- [ ] Task 3\n- [ ] Task 2" }) + end + + it 'does not create a system note' do + note = find_note('Marked the task **Task 2** as incomplete') + + expect(note).to be_nil + end + end + + context 'when a Task list with a completed item is totally replaced' do + before do + update_issue({ description: "- [ ] Task 1\n- [X] Task 2" }) + update_issue({ description: "- [ ] One\n- [ ] Two\n- [ ] Three" }) + end + + it 'does not create a system note referencing the position the old item' do + note = find_note('Marked the task **Two** as incomplete') + + expect(note).to be_nil + end + + it 'should not generate a new note at all' do + expect do + update_issue({ description: "- [ ] One\n- [ ] Two\n- [ ] Three" }) + end.not_to change { Note.count } + end + end + end + end end diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb index b3cbfd4b5b8..50d0c288790 100644 --- a/spec/services/merge_requests/close_service_spec.rb +++ b/spec/services/merge_requests/close_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe MergeRequests::CloseService do +describe MergeRequests::CloseService, services: true do let(:user) { create(:user) } let(:user2) { create(:user) } let(:merge_request) { create(:merge_request, assignee: user2) } @@ -18,7 +18,9 @@ describe MergeRequests::CloseService do before do allow(service).to receive(:execute_hooks) - @merge_request = service.execute(merge_request) + perform_enqueued_jobs do + @merge_request = service.execute(merge_request) + end end it { expect(@merge_request).to be_valid } diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb index cc64d69361e..be8f1676eeb 100644 --- a/spec/services/merge_requests/create_service_spec.rb +++ b/spec/services/merge_requests/create_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe MergeRequests::CreateService do +describe MergeRequests::CreateService, services: true do let(:project) { create(:project) } let(:user) { create(:user) } diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index 7483f51de03..ceb3f97280e 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe MergeRequests::MergeService do +describe MergeRequests::MergeService, services: true do let(:user) { create(:user) } let(:user2) { create(:user) } let(:merge_request) { create(:merge_request, assignee: user2) } @@ -13,12 +13,14 @@ describe MergeRequests::MergeService do describe :execute do context 'valid params' do - let(:service) { MergeRequests::MergeService.new(project, user, {}) } + let(:service) { MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message') } before do allow(service).to receive(:execute_hooks) - service.execute(merge_request, 'Awesome message') + perform_enqueued_jobs do + service.execute(merge_request) + end end it { expect(merge_request).to be_valid } @@ -37,14 +39,14 @@ describe MergeRequests::MergeService do end context "error handling" do - let(:service) { MergeRequests::MergeService.new(project, user, {}) } + let(:service) { MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message') } it 'saves error if there is an exception' do allow(service).to receive(:repository).and_raise("error") allow(service).to receive(:execute_hooks) - service.execute(merge_request, 'Awesome message') + service.execute(merge_request) expect(merge_request.merge_error).to eq("Something went wrong during merge") end diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb new file mode 100644 index 00000000000..188fda6211f --- /dev/null +++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb @@ -0,0 +1,84 @@ +require 'spec_helper' + +describe MergeRequests::MergeWhenBuildSucceedsService do + let(:user) { create(:user) } + let(:merge_request) { create(:merge_request) } + + let(:mr_merge_if_green_enabled) do + create(:merge_request, merge_when_build_succeeds: true, merge_user: user, + source_branch: "source_branch", target_branch: project.default_branch, + source_project: project, target_project: project, state: "opened") + end + + let(:project) { create(:project) } + let(:ci_commit) { create(:ci_commit_with_one_job, ref: mr_merge_if_green_enabled.source_branch, gl_project: project) } + let(:service) { MergeRequests::MergeWhenBuildSucceedsService.new(project, user, commit_message: 'Awesome message') } + + describe "#execute" do + context 'first time enabling' do + before do + allow(merge_request).to receive(:ci_commit).and_return(ci_commit) + service.execute(merge_request) + end + + it 'sets the params, merge_user, and flag' do + expect(merge_request).to be_valid + expect(merge_request.merge_when_build_succeeds).to be_truthy + expect(merge_request.merge_params).to eq commit_message: 'Awesome message' + expect(merge_request.merge_user).to be user + end + + it 'creates a system note' do + note = merge_request.notes.last + expect(note.note).to match /Enabled an automatic merge when the build for (\w+\/\w+@)?[0-9a-z]{8}/ + end + end + + context 'already approved' do + let(:service) { MergeRequests::MergeWhenBuildSucceedsService.new(project, user, new_key: true) } + let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch) } + + before do + allow(mr_merge_if_green_enabled).to receive(:ci_commit).and_return(ci_commit) + allow(mr_merge_if_green_enabled).to receive(:mergeable?).and_return(true) + allow(ci_commit).to receive(:success?).and_return(true) + end + + it 'updates the merge params' do + expect(SystemNoteService).not_to receive(:merge_when_build_succeeds) + + service.execute(mr_merge_if_green_enabled) + expect(mr_merge_if_green_enabled.merge_params).to have_key(:new_key) + end + end + end + + describe "#trigger" do + let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch, status: "success") } + + it "merges all merge requests with merge when build succeeds enabled" do + allow_any_instance_of(MergeRequest).to receive(:ci_commit).and_return(ci_commit) + allow(ci_commit).to receive(:success?).and_return(true) + + expect(MergeWorker).to receive(:perform_async) + service.trigger(build) + end + end + + describe "#cancel" do + before do + service.cancel(mr_merge_if_green_enabled) + end + + it "resets all the merge_when_build_succeeds params" do + expect(mr_merge_if_green_enabled.merge_when_build_succeeds).to be_falsey + expect(mr_merge_if_green_enabled.merge_params).to eq({}) + expect(mr_merge_if_green_enabled.merge_user).to be nil + end + + it 'Posts a system note' do + note = mr_merge_if_green_enabled.notes.last + expect(note.note).to include 'Canceled the automatic merge' + end + end +end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 7ee4488521d..450250ba032 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe MergeRequests::RefreshService do +describe MergeRequests::RefreshService, services: true do let(:project) { create(:project) } let(:user) { create(:user) } let(:service) { MergeRequests::RefreshService } @@ -17,7 +17,9 @@ describe MergeRequests::RefreshService do source_project: @project, source_branch: 'master', target_branch: 'feature', - target_project: @project) + target_project: @project, + merge_when_build_succeeds: true, + merge_user: @user) @fork_merge_request = create(:merge_request, source_project: @fork_project, @@ -46,6 +48,7 @@ describe MergeRequests::RefreshService do it { expect(@merge_request.notes).not_to be_empty } it { expect(@merge_request).to be_open } + it { expect(@merge_request.merge_when_build_succeeds).to be_falsey} it { expect(@fork_merge_request).to be_open } it { expect(@fork_merge_request.notes).to be_empty } end @@ -146,6 +149,7 @@ describe MergeRequests::RefreshService do end end + def reload_mrs @merge_request.reload @fork_merge_request.reload diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb index 9401bc3b558..ac0221998f5 100644 --- a/spec/services/merge_requests/reopen_service_spec.rb +++ b/spec/services/merge_requests/reopen_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe MergeRequests::ReopenService do +describe MergeRequests::ReopenService, services: true do let(:user) { create(:user) } let(:user2) { create(:user) } let(:merge_request) { create(:merge_request, assignee: user2) } @@ -19,7 +19,9 @@ describe MergeRequests::ReopenService do allow(service).to receive(:execute_hooks) merge_request.state = :closed - service.execute(merge_request) + perform_enqueued_jobs do + service.execute(merge_request) + end end it { expect(merge_request).to be_valid } diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index c75173c1452..2e9e6e0870d 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -1,9 +1,10 @@ require 'spec_helper' -describe MergeRequests::UpdateService do +describe MergeRequests::UpdateService, services: true do let(:user) { create(:user) } let(:user2) { create(:user) } - let(:merge_request) { create(:merge_request, :simple, title: 'Old title') } + let(:user3) { create(:user) } + let(:merge_request) { create(:merge_request, :simple, title: 'Old title', assignee_id: user3.id) } let(:project) { merge_request.project } let(:label) { create(:label) } @@ -13,6 +14,17 @@ describe MergeRequests::UpdateService do end describe 'execute' do + def find_note(starting_with) + @merge_request.notes.find do |note| + note && note.note.start_with?(starting_with) + end + end + + def update_merge_request(opts) + @merge_request = MergeRequests::UpdateService.new(project, user, opts).execute(merge_request) + @merge_request.reload + end + context 'valid params' do let(:opts) do { @@ -30,8 +42,10 @@ describe MergeRequests::UpdateService do before do allow(service).to receive(:execute_hooks) - @merge_request = service.execute(merge_request) - @merge_request.reload + perform_enqueued_jobs do + @merge_request = service.execute(merge_request) + @merge_request.reload + end end it { expect(@merge_request).to be_valid } @@ -47,18 +61,14 @@ describe MergeRequests::UpdateService do with(@merge_request, 'update') end - it 'should send email to user2 about assign of new merge_request' do - email = ActionMailer::Base.deliveries.last - expect(email.to.first).to eq(user2.email) + it 'should send email to user2 about assign of new merge request and email to user3 about merge request unassignment' do + deliveries = ActionMailer::Base.deliveries + email = deliveries.last + recipients = deliveries.last(2).map(&:to).flatten + expect(recipients).to include(user2.email, user3.email) expect(email.subject).to include(merge_request.title) end - def find_note(starting_with) - @merge_request.notes.find do |note| - note && note.note.start_with?(starting_with) - end - end - it 'should create system note about merge_request reassign' do note = find_note('Reassigned to') @@ -87,5 +97,39 @@ describe MergeRequests::UpdateService do expect(note.note).to eq 'Target branch changed from `master` to `target`' end end + + context 'when MergeRequest has tasks' do + before { update_merge_request({ description: "- [ ] Task 1\n- [ ] Task 2" }) } + + it { expect(@merge_request.tasks?).to eq(true) } + + context 'when tasks are marked as completed' do + before { update_merge_request({ description: "- [x] Task 1\n- [X] Task 2" }) } + + it 'creates system note about task status change' do + note1 = find_note('Marked the task **Task 1** as completed') + note2 = find_note('Marked the task **Task 2** as completed') + + expect(note1).not_to be_nil + expect(note2).not_to be_nil + end + end + + context 'when tasks are marked as incomplete' do + before do + update_merge_request({ description: "- [x] Task 1\n- [X] Task 2" }) + update_merge_request({ description: "- [ ] Task 1\n- [ ] Task 2" }) + end + + it 'creates system note about task status change' do + note1 = find_note('Marked the task **Task 1** as incomplete') + note2 = find_note('Marked the task **Task 2** as incomplete') + + expect(note1).not_to be_nil + expect(note2).not_to be_nil + end + end + end + end end diff --git a/spec/services/milestones/close_service_spec.rb b/spec/services/milestones/close_service_spec.rb new file mode 100644 index 00000000000..1cd6eb2ab38 --- /dev/null +++ b/spec/services/milestones/close_service_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe Milestones::CloseService, services: true do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:milestone) { create(:milestone, title: "Milestone v1.2", project: project) } + + before do + project.team << [user, :master] + end + + describe :execute do + before do + Milestones::CloseService.new(project, user, {}).execute(milestone) + end + + it { expect(milestone).to be_valid } + it { expect(milestone).to be_closed } + + describe :event do + let(:event) { Event.first } + + it { expect(event.milestone).to be_truthy } + it { expect(event.target).to eq(milestone) } + it { expect(event.action_name).to eq('closed') } + end + end +end diff --git a/spec/services/milestones/create_service_spec.rb b/spec/services/milestones/create_service_spec.rb new file mode 100644 index 00000000000..c793026e300 --- /dev/null +++ b/spec/services/milestones/create_service_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe Milestones::CreateService, services: true do + let(:project) { create(:empty_project) } + let(:user) { create(:user) } + + describe :execute do + context "valid params" do + before do + project.team << [user, :master] + + opts = { + title: 'v2.1.9', + description: 'Patch release to fix security issue' + } + + @milestone = Milestones::CreateService.new(project, user, opts).execute + end + + it { expect(@milestone).to be_valid } + it { expect(@milestone.title).to eq('v2.1.9') } + end + end +end diff --git a/spec/services/milestones/group_service_spec.rb b/spec/services/milestones/group_service_spec.rb deleted file mode 100644 index 74eb0f99e0f..00000000000 --- a/spec/services/milestones/group_service_spec.rb +++ /dev/null @@ -1,70 +0,0 @@ -require 'spec_helper' - -describe Milestones::GroupService do - let(:user) { create(:user) } - let(:user2) { create(:user) } - let(:group) { create(:group) } - let(:project1) { create(:project, group: group) } - let(:project2) { create(:project, path: 'gitlab-ci', group: group) } - let(:project3) { create(:project, path: 'cookbook-gitlab', group: group) } - let(:milestone1_project1) { create(:milestone, title: "Milestone v1.2", project: project1) } - let(:milestone1_project2) { create(:milestone, title: "Milestone v1.2", project: project2) } - let(:milestone1_project3) { create(:milestone, title: "Milestone v1.2", project: project3) } - let(:milestone2_project1) { create(:milestone, title: "VD-123", project: project1) } - let(:milestone2_project2) { create(:milestone, title: "VD-123", project: project2) } - let(:milestone2_project3) { create(:milestone, title: "VD-123", project: project3) } - - describe 'execute' do - context 'with valid projects' do - before do - milestones = - [ - milestone1_project1, - milestone1_project2, - milestone1_project3, - milestone2_project1, - milestone2_project2, - milestone2_project3 - ] - @group_milestones = Milestones::GroupService.new(milestones).execute - end - - it 'should have all project milestones' do - expect(@group_milestones.count).to eq(2) - end - - it 'should have all project milestones titles' do - expect(@group_milestones.map { |group_milestone| group_milestone.title }).to match_array(['Milestone v1.2', 'VD-123']) - end - - it 'should have all project milestones' do - expect(@group_milestones.map { |group_milestone| group_milestone.milestones.count }.sum).to eq(6) - end - end - end - - describe 'milestone' do - context 'with valid title' do - before do - milestones = - [ - milestone1_project1, - milestone1_project2, - milestone1_project3, - milestone2_project1, - milestone2_project2, - milestone2_project3 - ] - @group_milestones = Milestones::GroupService.new(milestones).milestone('Milestone v1.2') - end - - it 'should have exactly one group milestone' do - expect(@group_milestones.title).to eq('Milestone v1.2') - end - - it 'should have all project milestones with the same title' do - expect(@group_milestones.milestones.count).to eq(3) - end - end - end -end diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb index f2ea0805b2f..a797a2fe4aa 100644 --- a/spec/services/notes/create_service_spec.rb +++ b/spec/services/notes/create_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Notes::CreateService do +describe Notes::CreateService, services: true do let(:project) { create(:empty_project) } let(:issue) { create(:issue, project: project) } let(:user) { create(:user) } @@ -24,4 +24,38 @@ describe Notes::CreateService do it { expect(@note.note).to eq('Awesome comment') } end end + + describe "award emoji" do + before do + project.team << [user, :master] + end + + it "creates emoji note" do + opts = { + note: ':smile: ', + noteable_type: 'Issue', + noteable_id: issue.id + } + + @note = Notes::CreateService.new(project, user, opts).execute + + expect(@note).to be_valid + expect(@note.note).to eq('smile') + expect(@note.is_award).to be_truthy + end + + it "creates regular note if emoji name is invalid" do + opts = { + note: ':smile: moretext: ', + noteable_type: 'Issue', + noteable_id: issue.id + } + + @note = Notes::CreateService.new(project, user, opts).execute + + expect(@note).to be_valid + expect(@note.note).to eq(opts[:note]) + expect(@note.is_award).to be_falsy + end + end end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 520140917aa..d7a898e85ff 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -1,8 +1,14 @@ require 'spec_helper' -describe NotificationService do +describe NotificationService, services: true do let(:notification) { NotificationService.new } + around(:each) do |example| + perform_enqueued_jobs do + example.run + end + end + describe 'Keys' do describe :new_key do let!(:key) { create(:personal_key) } @@ -10,8 +16,7 @@ describe NotificationService do it { expect(notification.new_key(key)).to be_truthy } it 'should sent email to key owner' do - expect(Notify).to receive(:new_ssh_key_email).with(key.id) - notification.new_key(key) + expect{ notification.new_key(key) }.to change{ ActionMailer::Base.deliveries.size }.by(1) end end end @@ -23,8 +28,7 @@ describe NotificationService do it { expect(notification.new_email(email)).to be_truthy } it 'should send email to email owner' do - expect(Notify).to receive(:new_email_email).with(email.id) - notification.new_email(email) + expect{ notification.new_email(email) }.to change{ ActionMailer::Base.deliveries.size }.by(1) end end end @@ -41,24 +45,28 @@ describe NotificationService do project.team << [issue.author, :master] project.team << [issue.assignee, :master] project.team << [note.author, :master] + create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: '@subscribed_participant cc this guy') end describe :new_note do it do add_users_with_subscription(note.project, issue) - should_email(@u_watcher.id) - should_email(note.noteable.author_id) - should_email(note.noteable.assignee_id) - should_email(@u_mentioned.id) - should_email(@subscriber.id) - should_not_email(note.author_id) - should_not_email(@u_participating.id) - should_not_email(@u_disabled.id) - should_not_email(@unsubscriber.id) - should_not_email(@u_outsider_mentioned) + ActionMailer::Base.deliveries.clear notification.new_note(note) + + should_email(@u_watcher) + should_email(note.noteable.author) + should_email(note.noteable.assignee) + should_email(@u_mentioned) + should_email(@subscriber) + should_email(@subscribed_participant) + should_not_email(note.author) + should_not_email(@u_participating) + should_not_email(@u_disabled) + should_not_email(@unsubscriber) + should_not_email(@u_outsider_mentioned) end it 'filters out "mentioned in" notes' do @@ -82,26 +90,20 @@ describe NotificationService do group_member = note.project.group.group_members.find_by_user_id(@u_watcher.id) group_member.notification_level = Notification::N_GLOBAL group_member.save + ActionMailer::Base.deliveries.clear end it do - should_email(note.noteable.author_id) - should_email(note.noteable.assignee_id) - should_email(@u_mentioned.id) - should_not_email(@u_watcher.id) - should_not_email(note.author_id) - should_not_email(@u_participating.id) - should_not_email(@u_disabled.id) notification.new_note(note) - end - end - def should_email(user_id) - expect(Notify).to receive(:note_issue_email).with(user_id, note.id) - end - - def should_not_email(user_id) - expect(Notify).not_to receive(:note_issue_email).with(user_id, note.id) + should_email(note.noteable.author) + should_email(note.noteable.assignee) + should_email(@u_mentioned) + should_not_email(@u_watcher) + should_not_email(note.author) + should_not_email(@u_participating) + should_not_email(@u_disabled) + end end end @@ -113,24 +115,26 @@ describe NotificationService do before do build_team(note.project) + ActionMailer::Base.deliveries.clear end describe :new_note do it do + notification.new_note(note) + # Notify all team members note.project.team.members.each do |member| # User with disabled notification should not be notified next if member.id == @u_disabled.id - should_email(member.id) + should_email(member) end - should_email(note.noteable.author_id) - should_email(note.noteable.assignee_id) - should_not_email(note.author_id) - should_not_email(@u_mentioned.id) - should_not_email(@u_disabled.id) - should_not_email(@u_not_mentioned.id) - notification.new_note(note) + should_email(note.noteable.author) + should_email(note.noteable.assignee) + should_not_email(note.author) + should_email(@u_mentioned) + should_not_email(@u_disabled) + should_email(@u_not_mentioned) end it 'filters out "mentioned in" notes' do @@ -140,14 +144,6 @@ describe NotificationService do notification.new_note(mentioned_note) end end - - def should_email(user_id) - expect(Notify).to receive(:note_issue_email).with(user_id, note.id) - end - - def should_not_email(user_id) - expect(Notify).not_to receive(:note_issue_email).with(user_id, note.id) - end end context 'commit note' do @@ -156,43 +152,38 @@ describe NotificationService do before do build_team(note.project) + ActionMailer::Base.deliveries.clear allow_any_instance_of(Commit).to receive(:author).and_return(@u_committer) end - describe :new_note do + describe :new_note, :perform_enqueued_jobs do it do - should_email(@u_committer.id, note) - should_email(@u_watcher.id, note) - should_not_email(@u_mentioned.id, note) - should_not_email(note.author_id, note) - should_not_email(@u_participating.id, note) - should_not_email(@u_disabled.id, note) notification.new_note(note) + + should_email(@u_committer) + should_email(@u_watcher) + should_not_email(@u_mentioned) + should_not_email(note.author) + should_not_email(@u_participating) + should_not_email(@u_disabled) end it do note.update_attribute(:note, '@mention referenced') - should_email(@u_committer.id, note) - should_email(@u_watcher.id, note) - should_email(@u_mentioned.id, note) - should_not_email(note.author_id, note) - should_not_email(@u_participating.id, note) - should_not_email(@u_disabled.id, note) notification.new_note(note) + + should_email(@u_committer) + should_email(@u_watcher) + should_email(@u_mentioned) + should_not_email(note.author) + should_not_email(@u_participating) + should_not_email(@u_disabled) end it do @u_committer.update_attributes(notification_level: Notification::N_MENTION) - should_not_email(@u_committer.id, note) notification.new_note(note) - end - - def should_email(user_id, n) - expect(Notify).to receive(:note_commit_email).with(user_id, n.id) - end - - def should_not_email(user_id, n) - expect(Notify).not_to receive(:note_commit_email).with(user_id, n.id) + should_not_email(@u_committer) end end end @@ -205,99 +196,69 @@ describe NotificationService do before do build_team(issue.project) add_users_with_subscription(issue.project, issue) + ActionMailer::Base.deliveries.clear end describe :new_issue do it do - should_email(issue.assignee_id) - should_email(@u_watcher.id) - should_email(@u_participant_mentioned.id) - should_not_email(@u_mentioned.id) - should_not_email(@u_participating.id) - should_not_email(@u_disabled.id) notification.new_issue(issue, @u_disabled) + + should_email(issue.assignee) + should_email(@u_watcher) + should_email(@u_participant_mentioned) + should_not_email(@u_mentioned) + should_not_email(@u_participating) + should_not_email(@u_disabled) end it do issue.assignee.update_attributes(notification_level: Notification::N_MENTION) - should_not_email(issue.assignee_id) notification.new_issue(issue, @u_disabled) - end - - def should_email(user_id) - expect(Notify).to receive(:new_issue_email).with(user_id, issue.id) - end - def should_not_email(user_id) - expect(Notify).not_to receive(:new_issue_email).with(user_id, issue.id) + should_not_email(issue.assignee) end end describe :reassigned_issue do it 'should email new assignee' do - should_email(issue.assignee_id) - should_email(@u_watcher.id) - should_email(@u_participant_mentioned.id) - should_email(@subscriber.id) - should_not_email(@unsubscriber.id) - should_not_email(@u_participating.id) - should_not_email(@u_disabled.id) - notification.reassigned_issue(issue, @u_disabled) - end - - def should_email(user_id) - expect(Notify).to receive(:reassigned_issue_email).with(user_id, issue.id, nil, @u_disabled.id) - end - def should_not_email(user_id) - expect(Notify).not_to receive(:reassigned_issue_email).with(user_id, issue.id, issue.assignee_id, @u_disabled.id) + should_email(issue.assignee) + should_email(@u_watcher) + should_email(@u_participant_mentioned) + should_email(@subscriber) + should_not_email(@unsubscriber) + should_not_email(@u_participating) + should_not_email(@u_disabled) end end describe :close_issue do it 'should sent email to issue assignee and issue author' do - should_email(issue.assignee_id) - should_email(issue.author_id) - should_email(@u_watcher.id) - should_email(@u_participant_mentioned.id) - should_email(@subscriber.id) - should_not_email(@unsubscriber.id) - should_not_email(@u_participating.id) - should_not_email(@u_disabled.id) - notification.close_issue(issue, @u_disabled) - end - - def should_email(user_id) - expect(Notify).to receive(:closed_issue_email).with(user_id, issue.id, @u_disabled.id) - end - def should_not_email(user_id) - expect(Notify).not_to receive(:closed_issue_email).with(user_id, issue.id, @u_disabled.id) + should_email(issue.assignee) + should_email(issue.author) + should_email(@u_watcher) + should_email(@u_participant_mentioned) + should_email(@subscriber) + should_not_email(@unsubscriber) + should_not_email(@u_participating) + should_not_email(@u_disabled) end end describe :reopen_issue do it 'should send email to issue assignee and issue author' do - should_email(issue.assignee_id) - should_email(issue.author_id) - should_email(@u_watcher.id) - should_email(@u_participant_mentioned.id) - should_email(@subscriber.id) - should_not_email(@unsubscriber.id) - should_not_email(@u_participating.id) - should_not_email(@u_disabled.id) - notification.reopen_issue(issue, @u_disabled) - end - - def should_email(user_id) - expect(Notify).to receive(:issue_status_changed_email).with(user_id, issue.id, 'reopened', @u_disabled.id) - end - def should_not_email(user_id) - expect(Notify).not_to receive(:issue_status_changed_email).with(user_id, issue.id, 'reopened', @u_disabled.id) + should_email(issue.assignee) + should_email(issue.author) + should_email(@u_watcher) + should_email(@u_participant_mentioned) + should_email(@subscriber) + should_not_email(@unsubscriber) + should_not_email(@u_participating) end end end @@ -309,108 +270,74 @@ describe NotificationService do before do build_team(merge_request.target_project) add_users_with_subscription(merge_request.target_project, merge_request) + ActionMailer::Base.deliveries.clear end describe :new_merge_request do it do - should_email(merge_request.assignee_id) - should_email(@u_watcher.id) - should_email(@u_participant_mentioned.id) - should_not_email(@u_participating.id) - should_not_email(@u_disabled.id) notification.new_merge_request(merge_request, @u_disabled) - end - - def should_email(user_id) - expect(Notify).to receive(:new_merge_request_email).with(user_id, merge_request.id) - end - def should_not_email(user_id) - expect(Notify).not_to receive(:new_merge_request_email).with(user_id, merge_request.id) + should_email(merge_request.assignee) + should_email(@u_watcher) + should_email(@u_participant_mentioned) + should_not_email(@u_participating) + should_not_email(@u_disabled) end end describe :reassigned_merge_request do it do - should_email(merge_request.assignee_id) - should_email(@u_watcher.id) - should_email(@u_participant_mentioned.id) - should_email(@subscriber.id) - should_not_email(@unsubscriber.id) - should_not_email(@u_participating.id) - should_not_email(@u_disabled.id) notification.reassigned_merge_request(merge_request, merge_request.author) - end - def should_email(user_id) - expect(Notify).to receive(:reassigned_merge_request_email).with(user_id, merge_request.id, nil, merge_request.author_id) - end - - def should_not_email(user_id) - expect(Notify).not_to receive(:reassigned_merge_request_email).with(user_id, merge_request.id, merge_request.assignee_id, merge_request.author_id) + should_email(merge_request.assignee) + should_email(@u_watcher) + should_email(@u_participant_mentioned) + should_email(@subscriber) + should_not_email(@unsubscriber) + should_not_email(@u_participating) + should_not_email(@u_disabled) end end describe :closed_merge_request do it do - should_email(merge_request.assignee_id) - should_email(@u_watcher.id) - should_email(@u_participant_mentioned.id) - should_email(@subscriber.id) - should_not_email(@unsubscriber.id) - should_not_email(@u_participating.id) - should_not_email(@u_disabled.id) notification.close_mr(merge_request, @u_disabled) - end - - def should_email(user_id) - expect(Notify).to receive(:closed_merge_request_email).with(user_id, merge_request.id, @u_disabled.id) - end - def should_not_email(user_id) - expect(Notify).not_to receive(:closed_merge_request_email).with(user_id, merge_request.id, @u_disabled.id) + should_email(merge_request.assignee) + should_email(@u_watcher) + should_email(@u_participant_mentioned) + should_email(@subscriber) + should_not_email(@unsubscriber) + should_not_email(@u_participating) + should_not_email(@u_disabled) end end describe :merged_merge_request do it do - should_email(merge_request.assignee_id) - should_email(@u_watcher.id) - should_email(@u_participant_mentioned.id) - should_email(@subscriber.id) - should_not_email(@unsubscriber.id) - should_not_email(@u_participating.id) - should_not_email(@u_disabled.id) notification.merge_mr(merge_request, @u_disabled) - end - - def should_email(user_id) - expect(Notify).to receive(:merged_merge_request_email).with(user_id, merge_request.id, @u_disabled.id) - end - def should_not_email(user_id) - expect(Notify).not_to receive(:merged_merge_request_email).with(user_id, merge_request.id, @u_disabled.id) + should_email(merge_request.assignee) + should_email(@u_watcher) + should_email(@u_participant_mentioned) + should_email(@subscriber) + should_not_email(@unsubscriber) + should_not_email(@u_participating) + should_not_email(@u_disabled) end end describe :reopen_merge_request do it do - should_email(merge_request.assignee_id) - should_email(@u_watcher.id) - should_email(@u_participant_mentioned.id) - should_email(@subscriber.id) - should_not_email(@unsubscriber.id) - should_not_email(@u_participating.id) - should_not_email(@u_disabled.id) notification.reopen_mr(merge_request, @u_disabled) - end - - def should_email(user_id) - expect(Notify).to receive(:merge_request_status_email).with(user_id, merge_request.id, 'reopened', @u_disabled.id) - end - def should_not_email(user_id) - expect(Notify).not_to receive(:merge_request_status_email).with(user_id, merge_request.id, 'reopened', @u_disabled.id) + should_email(merge_request.assignee) + should_email(@u_watcher) + should_email(@u_participant_mentioned) + should_email(@subscriber) + should_not_email(@unsubscriber) + should_not_email(@u_participating) + should_not_email(@u_disabled) end end end @@ -420,22 +347,16 @@ describe NotificationService do before do build_team(project) + ActionMailer::Base.deliveries.clear end describe :project_was_moved do it do - should_email(@u_watcher.id) - should_email(@u_participating.id) - should_not_email(@u_disabled.id) notification.project_was_moved(project, "gitlab/gitlab") - end - def should_email(user_id) - expect(Notify).to receive(:project_was_moved_email).with(project.id, user_id, "gitlab/gitlab") - end - - def should_not_email(user_id) - expect(Notify).not_to receive(:project_was_moved_email).with(project.id, user_id, "gitlab/gitlab") + should_email(@u_watcher) + should_email(@u_participating) + should_not_email(@u_disabled) end end end @@ -462,11 +383,26 @@ describe NotificationService do def add_users_with_subscription(project, issuable) @subscriber = create :user @unsubscriber = create :user + @subscribed_participant = create(:user, username: 'subscribed_participant', notification_level: Notification::N_PARTICIPATING) + project.team << [@subscribed_participant, :master] project.team << [@subscriber, :master] project.team << [@unsubscriber, :master] issuable.subscriptions.create(user: @subscriber, subscribed: true) + issuable.subscriptions.create(user: @subscribed_participant, subscribed: true) issuable.subscriptions.create(user: @unsubscriber, subscribed: false) 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/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index 25277f07482..2d8c316e38d 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Projects::CreateService do +describe Projects::CreateService, services: true do describe :create_by_user do before do @user = create :user @@ -70,6 +70,28 @@ describe Projects::CreateService do end end + context 'builds_enabled global setting' do + let(:project) { create_project(@user, @opts) } + + subject { project.builds_enabled? } + + context 'global builds_enabled false does not enable CI by default' do + before do + @opts.merge!(builds_enabled: false) + end + + it { is_expected.to be_falsey } + end + + context 'global builds_enabled true does enable CI by default' do + before do + @opts.merge!(builds_enabled: true) + end + + it { is_expected.to be_truthy } + end + end + context 'restricted visibility level' do before do stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb index e83eef0b1a2..1ec27077717 100644 --- a/spec/services/projects/destroy_service_spec.rb +++ b/spec/services/projects/destroy_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Projects::DestroyService do +describe Projects::DestroyService, services: true do let!(:user) { create(:user) } let!(:project) { create(:project, namespace: user.namespace) } let!(:path) { project.repository.path_to_repo } diff --git a/spec/services/projects/download_service_spec.rb b/spec/services/projects/download_service_spec.rb index ddee2e62dfc..5ceed5af9a5 100644 --- a/spec/services/projects/download_service_spec.rb +++ b/spec/services/projects/download_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Projects::DownloadService do +describe Projects::DownloadService, services: true do describe 'File service' do before do @user = create :user diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb index 65a8c81204d..d1ee60a0aea 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Projects::ForkService do +describe Projects::ForkService, services: true do describe :fork_by_user do before do @from_namespace = create(:namespace) @@ -25,13 +25,6 @@ describe Projects::ForkService do end end - context 'fork project failure' do - it "fails due to transaction failure" do - @to_project = fork_project(@from_project, @to_user, false) - expect(@to_project.import_failed?) - end - end - context 'project already exists' do it "should fail due to validation, not transaction failure" do @existing_project = create(:project, creator_id: @to_user.id, name: @from_project.name, namespace: @to_namespace) @@ -46,7 +39,7 @@ describe Projects::ForkService do it "fork and enable CI for fork" do @from_project.enable_ci @to_project = fork_project(@from_project, @to_user) - expect(@to_project.gitlab_ci?).to be_truthy + expect(@to_project.builds_enabled?).to be_truthy end end end @@ -66,7 +59,7 @@ describe Projects::ForkService do context 'fork project for group' do it 'group owner successfully forks project into the group' do - to_project = fork_project(@project, @group_owner, true, @opts) + to_project = fork_project(@project, @group_owner, @opts) expect(to_project.owner).to eq(@group) expect(to_project.namespace).to eq(@group) expect(to_project.name).to eq(@project.name) @@ -78,7 +71,7 @@ describe Projects::ForkService do context 'fork project for group when user not owner' do it 'group developer should fail to fork project into the group' do - to_project = fork_project(@project, @developer, true, @opts) + to_project = fork_project(@project, @developer, @opts) expect(to_project.errors[:namespace]).to eq(['is not valid']) end end @@ -87,7 +80,7 @@ describe Projects::ForkService do it 'should fail due to validation, not transaction failure' do existing_project = create(:project, name: @project.name, namespace: @group) - to_project = fork_project(@project, @group_owner, true, @opts) + to_project = fork_project(@project, @group_owner, @opts) expect(existing_project.persisted?).to be_truthy expect(to_project.errors[:name]).to eq(['has already been taken']) expect(to_project.errors[:path]).to eq(['has already been taken']) @@ -95,8 +88,8 @@ describe Projects::ForkService do end end - def fork_project(from_project, user, fork_success = true, params = {}) - allow(RepositoryForkWorker).to receive(:perform_async).and_return(fork_success) + def fork_project(from_project, user, params = {}) + allow(RepositoryForkWorker).to receive(:perform_async).and_return(true) Projects::ForkService.new(from_project, user, params).execute end end diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index 47755bfc990..c46259431aa 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Projects::TransferService do +describe Projects::TransferService, services: true do let(:user) { create(:user) } let(:group) { create(:group) } let(:project) { create(:project, namespace: user.namespace) } diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index b347fa15f87..c36d4581989 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Projects::UpdateService do +describe Projects::UpdateService, services: true do describe :update_by_user do before do @user = create :user diff --git a/spec/services/projects/upload_service_spec.rb b/spec/services/projects/upload_service_spec.rb index 1b1a80d1fe7..9268a9fb1a2 100644 --- a/spec/services/projects/upload_service_spec.rb +++ b/spec/services/projects/upload_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Projects::UploadService do +describe Projects::UploadService, services: true do describe 'File service' do before do @user = create :user diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb index f57bfaea879..7b3a9a75d7c 100644 --- a/spec/services/search_service_spec.rb +++ b/spec/services/search_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Search::GlobalService' do +describe 'Search::GlobalService', services: true do let(:user) { create(:user) } let(:public_user) { create(:user) } let(:internal_user) { create(:user) } diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb index a31fc1e4b07..febc78d2784 100644 --- a/spec/services/system_hooks_service_spec.rb +++ b/spec/services/system_hooks_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe SystemHooksService do +describe SystemHooksService, services: true do let(:user) { create :user } let(:project) { create :project } let(:project_member) { create :project_member } diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index a45130bd473..0a4f9b230e8 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe SystemNoteService do +describe SystemNoteService, services: true do let(:project) { create(:project) } let(:author) { create(:user) } let(:noteable) { create(:issue, project: project) } @@ -207,6 +207,32 @@ describe SystemNoteService do end end + describe '.merge_when_build_succeeds' do + let(:ci_commit) { build :ci_commit_without_jobs } + let(:noteable) { create :merge_request } + + subject { described_class.merge_when_build_succeeds(noteable, project, author, noteable.last_commit) } + + it_behaves_like 'a system note' + + it "posts the Merge When Build Succeeds system note" do + expect(subject.note).to match /Enabled an automatic merge when the build for (\w+\/\w+@)?[0-9a-f]{40} succeeds/ + end + end + + describe '.cancel_merge_when_build_succeeds' do + let(:ci_commit) { build :ci_commit_without_jobs } + let(:noteable) { create :merge_request } + + subject { described_class.cancel_merge_when_build_succeeds(noteable, project, author) } + + it_behaves_like 'a system note' + + it "posts the Merge When Build Succeeds system note" do + expect(subject.note).to eq "Canceled the automatic merge" + end + end + describe '.change_title' do subject { described_class.change_title(noteable, project, author, 'Old title') } diff --git a/spec/services/test_hook_service_spec.rb b/spec/services/test_hook_service_spec.rb index 226196eedae..f034f251ba4 100644 --- a/spec/services/test_hook_service_spec.rb +++ b/spec/services/test_hook_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe TestHookService do +describe TestHookService, services: true do let(:user) { create :user } let(:project) { create :project } let(:hook) { create :project_hook, project: project } diff --git a/spec/services/update_release_service_spec.rb b/spec/services/update_release_service_spec.rb new file mode 100644 index 00000000000..bba211089a8 --- /dev/null +++ b/spec/services/update_release_service_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe UpdateReleaseService, services: true do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:tag_name) { project.repository.tag_names.first } + let(:description) { 'Awesome release!' } + let(:new_description) { 'The best release!' } + let(:service) { UpdateReleaseService.new(project, user) } + + context 'with an existing release' do + let(:create_service) { CreateReleaseService.new(project, user) } + + before do + create_service.execute(tag_name, description) + end + + it 'successfully updates an existing release' do + result = service.execute(tag_name, new_description) + expect(result[:status]).to eq(:success) + expect(project.releases.find_by(tag: tag_name).description).to eq(new_description) + end + end + + it 'raises an error if the tag does not exist' do + result = service.execute("foobar", description) + expect(result[:status]).to eq(:error) + end + + it 'raises an error if the release does not exist' do + result = service.execute(tag_name, description) + expect(result[:status]).to eq(:error) + end +end diff --git a/spec/services/update_snippet_service_spec.rb b/spec/services/update_snippet_service_spec.rb index d7c516e3934..124bb76e678 100644 --- a/spec/services/update_snippet_service_spec.rb +++ b/spec/services/update_snippet_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe UpdateSnippetService do +describe UpdateSnippetService, services: true do before do @user = create :user @admin = create :user, admin: true diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 2be13bb3e6a..0225a0ee53f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -31,6 +31,7 @@ RSpec.configure do |config| config.include StubConfiguration config.include RelativeUrl, type: feature config.include TestEnv + config.include ActiveJob::TestHelper config.include StubGitlabCalls config.include StubGitlabData config.include BenchmarkMatchers, benchmark: true diff --git a/spec/support/filter_spec_helper.rb b/spec/support/filter_spec_helper.rb index 97e5c270a59..91e3bee13c1 100644 --- a/spec/support/filter_spec_helper.rb +++ b/spec/support/filter_spec_helper.rb @@ -35,11 +35,24 @@ module FilterSpecHelper pipeline.call(body) end - def reference_pipeline_result(body, contexts = {}) + def reference_pipeline(contexts = {}) contexts.reverse_merge!(project: project) if defined?(project) - pipeline = HTML::Pipeline.new([described_class, Gitlab::Markdown::ReferenceGathererFilter], contexts) - pipeline.call(body) + filters = [ + Gitlab::Markdown::AutolinkFilter, + described_class, + Gitlab::Markdown::ReferenceGathererFilter + ] + + HTML::Pipeline.new(filters, contexts) + end + + def reference_pipeline_result(body, contexts = {}) + reference_pipeline(contexts).call(body) + end + + def reference_filter(html, contexts = {}) + reference_pipeline(contexts).to_document(html) end # Modify a String reference to make it invalid diff --git a/spec/support/markdown_feature.rb b/spec/support/markdown_feature.rb index bedc1a7f1db..d6d3062a197 100644 --- a/spec/support/markdown_feature.rb +++ b/spec/support/markdown_feature.rb @@ -93,6 +93,10 @@ class MarkdownFeature end end + def urls + Gitlab::Application.routes.url_helpers + end + def raw_markdown markdown = File.read(Rails.root.join('spec/fixtures/markdown.md.erb')) ERB.new(markdown).result(binding) diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb index 7500d0fdf80..7eadcd58c1f 100644 --- a/spec/support/matchers/markdown_matchers.rb +++ b/spec/support/matchers/markdown_matchers.rb @@ -71,7 +71,7 @@ module MarkdownMatchers set_default_markdown_messages match do |actual| - expect(actual).to have_selector('a.gfm.gfm-project_member', count: 3) + expect(actual).to have_selector('a.gfm.gfm-project_member', count: 4) end end @@ -80,7 +80,7 @@ module MarkdownMatchers set_default_markdown_messages match do |actual| - expect(actual).to have_selector('a.gfm.gfm-issue', count: 3) + expect(actual).to have_selector('a.gfm.gfm-issue', count: 6) end end @@ -89,7 +89,7 @@ module MarkdownMatchers set_default_markdown_messages match do |actual| - expect(actual).to have_selector('a.gfm.gfm-merge_request', count: 3) + expect(actual).to have_selector('a.gfm.gfm-merge_request', count: 6) expect(actual).to have_selector('em a.gfm-merge_request') end end @@ -99,7 +99,7 @@ module MarkdownMatchers set_default_markdown_messages match do |actual| - expect(actual).to have_selector('a.gfm.gfm-snippet', count: 2) + expect(actual).to have_selector('a.gfm.gfm-snippet', count: 5) end end @@ -108,7 +108,7 @@ module MarkdownMatchers set_default_markdown_messages match do |actual| - expect(actual).to have_selector('a.gfm.gfm-commit_range', count: 2) + expect(actual).to have_selector('a.gfm.gfm-commit_range', count: 5) end end @@ -117,7 +117,7 @@ module MarkdownMatchers set_default_markdown_messages match do |actual| - expect(actual).to have_selector('a.gfm.gfm-commit', count: 2) + expect(actual).to have_selector('a.gfm.gfm-commit', count: 5) end end @@ -126,7 +126,7 @@ module MarkdownMatchers set_default_markdown_messages match do |actual| - expect(actual).to have_selector('a.gfm.gfm-label', count: 3) + expect(actual).to have_selector('a.gfm.gfm-label', count: 4) end end diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb index 3bb568f4d49..33d2b14583c 100644 --- a/spec/support/mentionable_shared_examples.rb +++ b/spec/support/mentionable_shared_examples.rb @@ -10,12 +10,12 @@ def common_mentionable_setup let(:mentioned_issue) { create(:issue, project: project) } let!(:mentioned_mr) { create(:merge_request, :simple, source_project: project) } - let(:mentioned_commit) { project.commit } + let(:mentioned_commit) { project.commit("HEAD~1") } let(:ext_proj) { create(:project, :public) } let(:ext_issue) { create(:issue, project: ext_proj) } let(:ext_mr) { create(:merge_request, :simple, source_project: ext_proj) } - let(:ext_commit) { ext_proj.commit } + let(:ext_commit) { ext_proj.commit("HEAD~2") } # Override to add known commits to the repository stub. let(:extra_commits) { [] } @@ -45,14 +45,11 @@ def common_mentionable_setup before do # Wire the project's repository to return the mentioned commit, and +nil+ # for any unrecognized commits. - commitmap = { - mentioned_commit.id => mentioned_commit - } - extra_commits.each { |c| commitmap[c.short_id] = c } - - allow(Project).to receive(:find).and_call_original - allow(Project).to receive(:find).with(project.id.to_s).and_return(project) - allow(project.repository).to receive(:commit) { |sha| commitmap[sha] } + allow_any_instance_of(::Repository).to receive(:commit).and_call_original + allow_any_instance_of(::Repository).to receive(:commit).with(mentioned_commit.short_id).and_return(mentioned_commit) + extra_commits.each do |commit| + allow_any_instance_of(::Repository).to receive(:commit).with(commit.short_id).and_return(commit) + end set_mentionable_text.call(ref_string) end diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index 787670e9297..4f4743bff6d 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -12,6 +12,7 @@ module TestEnv 'fix' => '48f0be4', 'improve/awesome' => '5937ac0', 'markdown' => '0ed8c6c', + 'lfs' => 'be93687', 'master' => '5937ac0', "'test'" => 'e56497b', } @@ -21,7 +22,8 @@ module TestEnv # We currently only need a subset of the branches FORKED_BRANCH_SHA = { 'add-submodule-version-bump' => '3f547c08', - 'master' => '5937ac0' + 'master' => '5937ac0', + 'remove-submodule' => '2a33e0c0' } # Test environment diff --git a/spec/support/wait_for_ajax.rb b/spec/support/wait_for_ajax.rb new file mode 100644 index 00000000000..692d219e9f1 --- /dev/null +++ b/spec/support/wait_for_ajax.rb @@ -0,0 +1,11 @@ +module WaitForAjax + def wait_for_ajax + Timeout.timeout(Capybara.default_wait_time) do + loop until finished_all_ajax_requests? + end + end + + def finished_all_ajax_requests? + page.evaluate_script('jQuery.active').zero? + end +end diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index 386ac9c8372..63bed2414df 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -16,7 +16,7 @@ describe 'gitlab:app namespace rake task' do end def reenable_backup_sub_tasks - %w{db repo uploads builds}.each do |subtask| + %w{db repo uploads builds artifacts lfs}.each do |subtask| Rake::Task["gitlab:backup:#{subtask}:create"].reenable end end @@ -49,13 +49,15 @@ describe 'gitlab:app namespace rake task' do to raise_error(SystemExit) end - it 'should invoke restoration on mach' do + it 'should invoke restoration on match' do allow(YAML).to receive(:load_file). and_return({ gitlab_version: gitlab_version }) expect(Rake::Task["gitlab:backup:db:restore"]).to receive(:invoke) expect(Rake::Task["gitlab:backup:repo:restore"]).to receive(:invoke) expect(Rake::Task["gitlab:backup:builds:restore"]).to receive(:invoke) expect(Rake::Task["gitlab:backup:uploads:restore"]).to receive(:invoke) + expect(Rake::Task["gitlab:backup:artifacts:restore"]).to receive(:invoke) + expect(Rake::Task["gitlab:backup:lfs:restore"]).to receive(:invoke) expect(Rake::Task["gitlab:shell:setup"]).to receive(:invoke) expect { run_rake_task('gitlab:backup:restore') }.not_to raise_error end @@ -113,19 +115,21 @@ describe 'gitlab:app namespace rake task' do it 'should set correct permissions on the tar contents' do tar_contents, exit_status = Gitlab::Popen.popen( - %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz} + %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz lfs.tar.gz} ) expect(exit_status).to eq(0) expect(tar_contents).to match('db/') expect(tar_contents).to match('uploads.tar.gz') expect(tar_contents).to match('repositories/') expect(tar_contents).to match('builds.tar.gz') - expect(tar_contents).not_to match(/^.{4,9}[rwx].* (database.sql.gz|uploads.tar.gz|repositories|builds.tar.gz)\/$/) + expect(tar_contents).to match('artifacts.tar.gz') + expect(tar_contents).to match('lfs.tar.gz') + expect(tar_contents).not_to match(/^.{4,9}[rwx].* (database.sql.gz|uploads.tar.gz|repositories|builds.tar.gz|artifacts.tar.gz)\/$/) end it 'should delete temp directories' do temp_dirs = Dir.glob( - File.join(Gitlab.config.backup.path, '{db,repositories,uploads,builds}') + File.join(Gitlab.config.backup.path, '{db,repositories,uploads,builds,artifacts,lfs}') ) expect(temp_dirs).to be_empty @@ -147,7 +151,7 @@ describe 'gitlab:app namespace rake task' do # Redirect STDOUT and run the rake task orig_stdout = $stdout $stdout = StringIO.new - ENV["SKIP"] = "repositories" + ENV["SKIP"] = "repositories,uploads" run_rake_task('gitlab:backup:create') $stdout = orig_stdout @@ -161,12 +165,14 @@ describe 'gitlab:app namespace rake task' do it "does not contain skipped item" do tar_contents, _exit_status = Gitlab::Popen.popen( - %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz} + %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz lfs.tar.gz} ) expect(tar_contents).to match('db/') expect(tar_contents).to match('uploads.tar.gz') expect(tar_contents).to match('builds.tar.gz') + expect(tar_contents).to match('artifacts.tar.gz') + expect(tar_contents).to match('lfs.tar.gz') expect(tar_contents).not_to match('repositories/') end @@ -177,7 +183,10 @@ describe 'gitlab:app namespace rake task' do expect(Rake::Task["gitlab:backup:db:restore"]).to receive :invoke expect(Rake::Task["gitlab:backup:repo:restore"]).not_to receive :invoke + expect(Rake::Task["gitlab:backup:uploads:restore"]).not_to receive :invoke expect(Rake::Task["gitlab:backup:builds:restore"]).to receive :invoke + expect(Rake::Task["gitlab:backup:artifacts:restore"]).to receive :invoke + expect(Rake::Task["gitlab:backup:lfs:restore"]).to receive :invoke expect(Rake::Task["gitlab:shell:setup"]).to receive :invoke expect { run_rake_task('gitlab:backup:restore') }.not_to raise_error end diff --git a/spec/workers/email_receiver_worker_spec.rb b/spec/workers/email_receiver_worker_spec.rb index 65a8d7d9197..de40a6f78af 100644 --- a/spec/workers/email_receiver_worker_spec.rb +++ b/spec/workers/email_receiver_worker_spec.rb @@ -21,12 +21,14 @@ describe EmailReceiverWorker do end it "sends out a rejection email" do - described_class.new.perform(raw_message) - - email = ActionMailer::Base.deliveries.last - expect(email).not_to be_nil - expect(email.to).to eq(["jake@adventuretime.ooo"]) - expect(email.subject).to include("Rejected") + perform_enqueued_jobs do + described_class.new.perform(raw_message) + + email = ActionMailer::Base.deliveries.last + expect(email).not_to be_nil + expect(email.to).to eq(["jake@adventuretime.ooo"]) + expect(email.subject).to include("Rejected") + end end end end diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb index aa031106968..245f066df1f 100644 --- a/spec/workers/repository_fork_worker_spec.rb +++ b/spec/workers/repository_fork_worker_spec.rb @@ -12,7 +12,6 @@ describe RepositoryForkWorker do project.path_with_namespace, fork_project.namespace.path). and_return(true) - expect(ProjectCacheWorker).to receive(:perform_async) subject.perform(project.id, project.path_with_namespace, |
