diff options
author | Jacob Vosmaer <contact@jacobvosmaer.nl> | 2016-04-12 18:23:42 +0200 |
---|---|---|
committer | Jacob Vosmaer <contact@jacobvosmaer.nl> | 2016-04-12 18:23:42 +0200 |
commit | a1c8fdfbeef5a74dfdf86f1e7954a7a8eb8577eb (patch) | |
tree | 528c69cf876d2473fd9cfbfcf94d58a938fe0f7f /spec | |
parent | ab9dfa8fd681ac558cf988aa2cdb5bd69feea757 (diff) | |
parent | 2082879d2f3f91b038863f7c67c658d678924564 (diff) | |
download | gitlab-ce-a1c8fdfbeef5a74dfdf86f1e7954a7a8eb8577eb.tar.gz |
Merge branch 'master' of https://gitlab.com/gitlab-org/gitlab-ce into git-http-controller
Diffstat (limited to 'spec')
89 files changed, 2727 insertions, 375 deletions
diff --git a/spec/controllers/admin/projects_controller_spec.rb b/spec/controllers/admin/projects_controller_spec.rb new file mode 100644 index 00000000000..2ba0d489197 --- /dev/null +++ b/spec/controllers/admin/projects_controller_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe Admin::ProjectsController do + let!(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } + + before do + sign_in(create(:admin)) + end + + describe 'GET /projects' do + render_views + + it 'retrieves the project for the given visibility level' do + get :index, visibility_levels: [Gitlab::VisibilityLevel::PUBLIC] + expect(response.body).to match(project.name) + end + + it 'does not retrieve the project' do + get :index, visibility_levels: [Gitlab::VisibilityLevel::INTERNAL] + expect(response.body).to_not match(project.name) + end + end +end diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb index 5b1f65d7aff..9ef8ba1b097 100644 --- a/spec/controllers/admin/users_controller_spec.rb +++ b/spec/controllers/admin/users_controller_spec.rb @@ -1,15 +1,14 @@ require 'spec_helper' describe Admin::UsersController do - let(:admin) { create(:admin) } + let(:user) { create(:user) } before do - sign_in(admin) + sign_in(create(:admin)) end describe 'DELETE #user with projects' do - let(:user) { create(:user) } - let(:project) { create(:project, namespace: user.namespace) } + let(:project) { create(:empty_project, namespace: user.namespace) } before do project.team << [user, :developer] @@ -23,8 +22,6 @@ describe Admin::UsersController do end describe 'PUT block/:id' do - let(:user) { create(:user) } - it 'blocks user' do put :block, id: user.username user.reload @@ -50,8 +47,6 @@ describe Admin::UsersController do end context 'manually blocked users' do - let(:user) { create(:user) } - before do user.block end @@ -66,8 +61,6 @@ describe Admin::UsersController do end describe 'PUT unlock/:id' do - let(:user) { create(:user) } - before do request.env["HTTP_REFERER"] = "/" user.lock_access! @@ -95,8 +88,6 @@ describe Admin::UsersController do end describe 'PATCH disable_two_factor' do - let(:user) { create(:user) } - it 'disables 2FA for the user' do expect(user).to receive(:disable_two_factor!) allow(subject).to receive(:user).and_return(user) diff --git a/spec/controllers/ci/projects_controller_spec.rb b/spec/controllers/ci/projects_controller_spec.rb index db0748f323f..5022a3e2c80 100644 --- a/spec/controllers/ci/projects_controller_spec.rb +++ b/spec/controllers/ci/projects_controller_spec.rb @@ -5,6 +5,27 @@ describe Ci::ProjectsController do let!(:project) { create(:project, visibility, ci_id: 1) } let(:ci_id) { project.ci_id } + describe '#index' do + context 'user signed in' do + before do + sign_in(create(:user)) + get(:index) + end + + it 'redirects to /' do + expect(response).to redirect_to(root_path) + end + end + + context 'user not signed in' do + before { get(:index) } + + it 'redirects to sign in page' do + expect(response).to redirect_to(new_user_session_path) + end + end + end + ## # Specs for *deprecated* CI badge # diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb index eb0c6ac6d80..b0793cb1655 100644 --- a/spec/controllers/groups/milestones_controller_spec.rb +++ b/spec/controllers/groups/milestones_controller_spec.rb @@ -23,5 +23,11 @@ describe Groups::MilestonesController do 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 + + it "redirects to new when there are no project ids" do + post :create, group_id: group.id, milestone: { title: title, project_ids: [""] } + expect(response).to render_template :new + expect(assigns(:milestone).errors).not_to be_nil + end end end diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb index 98ae424ed7c..8ad73472117 100644 --- a/spec/controllers/projects/branches_controller_spec.rb +++ b/spec/controllers/projects/branches_controller_spec.rb @@ -93,6 +93,20 @@ describe Projects::BranchesController do end end + describe "POST destroy with HTML format" do + render_views + + it 'returns 303' do + post :destroy, + format: :html, + id: 'foo/bar/baz', + namespace_id: project.namespace.to_param, + project_id: project.to_param + + expect(response.status).to eq(303) + end + end + describe "POST destroy" do render_views diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index c5b034dc064..75e6b6f45a7 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -63,7 +63,7 @@ describe Projects::MergeRequestsController do id: merge_request.iid, format: format) - expect(response.body).to eq((merge_request.send(:"to_#{format}",user)).to_s) + expect(response.body).to eq((merge_request.send(:"to_#{format}")).to_s) end it "should not escape Html" do diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb new file mode 100644 index 00000000000..d47e4ab9a4f --- /dev/null +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -0,0 +1,49 @@ +require('spec_helper') + +describe Projects::ProjectMembersController do + let(:project) { create(:project) } + let(:another_project) { create(:project, :private) } + let(:user) { create(:user) } + let(:member) { create(:user) } + + before do + project.team << [user, :master] + another_project.team << [member, :guest] + sign_in(user) + end + + describe '#apply_import' do + shared_context 'import applied' do + before do + post(:apply_import, namespace_id: project.namespace.to_param, + project_id: project.to_param, + source_project_id: another_project.id) + end + end + + context 'when user can access source project members' do + before { another_project.team << [user, :guest] } + include_context 'import applied' + + it 'imports source project members' do + expect(project.team_members).to include member + expect(response).to set_flash.to 'Successfully imported' + expect(response).to redirect_to( + namespace_project_project_members_path(project.namespace, project) + ) + end + end + + context 'when user is not member of a source project' do + include_context 'import applied' + + it 'does not import team members' do + expect(project.team_members).to_not include member + end + + it 'responds with not found' do + expect(response.status).to eq 404 + end + end + end +end diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb new file mode 100644 index 00000000000..0f32a30f18b --- /dev/null +++ b/spec/controllers/projects/snippets_controller_spec.rb @@ -0,0 +1,107 @@ +require 'spec_helper' + +describe Projects::SnippetsController do + let(:project) { create(:project_empty_repo, :public, snippets_enabled: true) } + let(:user) { create(:user) } + let(:user2) { create(:user) } + + before do + project.team << [user, :master] + project.team << [user2, :master] + end + + describe 'GET #index' do + context 'when the project snippet is private' do + let!(:project_snippet) { create(:project_snippet, :private, project: project, author: user) } + + context 'when anonymous' do + it 'does not include the private snippet' do + get :index, namespace_id: project.namespace.path, project_id: project.path + + expect(assigns(:snippets)).not_to include(project_snippet) + expect(response.status).to eq(200) + end + end + + context 'when signed in as the author' do + before { sign_in(user) } + + it 'renders the snippet' do + get :index, namespace_id: project.namespace.path, project_id: project.path + + expect(assigns(:snippets)).to include(project_snippet) + expect(response.status).to eq(200) + end + end + + context 'when signed in as a project member' do + before { sign_in(user2) } + + it 'renders the snippet' do + get :index, namespace_id: project.namespace.path, project_id: project.path + + expect(assigns(:snippets)).to include(project_snippet) + expect(response.status).to eq(200) + end + end + end + end + + %w[show raw].each do |action| + describe "GET ##{action}" do + context 'when the project snippet is private' do + let(:project_snippet) { create(:project_snippet, :private, project: project, author: user) } + + context 'when anonymous' do + it 'responds with status 404' do + get action, namespace_id: project.namespace.path, project_id: project.path, id: project_snippet.to_param + + expect(response.status).to eq(404) + end + end + + context 'when signed in as the author' do + before { sign_in(user) } + + it 'renders the snippet' do + get action, namespace_id: project.namespace.path, project_id: project.path, id: project_snippet.to_param + + expect(assigns(:snippet)).to eq(project_snippet) + expect(response.status).to eq(200) + end + end + + context 'when signed in as a project member' do + before { sign_in(user2) } + + it 'renders the snippet' do + get action, namespace_id: project.namespace.path, project_id: project.path, id: project_snippet.to_param + + expect(assigns(:snippet)).to eq(project_snippet) + expect(response.status).to eq(200) + end + end + end + + context 'when the project snippet does not exist' do + context 'when anonymous' do + it 'responds with status 404' do + get action, namespace_id: project.namespace.path, project_id: project.path, id: 42 + + expect(response.status).to eq(404) + end + end + + context 'when signed in' do + before { sign_in(user) } + + it 'responds with status 404' do + get action, namespace_id: project.namespace.path, project_id: project.path, id: 42 + + expect(response.status).to eq(404) + end + end + end + end + end +end diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 1893e946f5c..069cd917e5a 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -83,6 +83,28 @@ describe ProjectsController do end end + describe "#update" do + render_views + + let(:admin) { create(:admin) } + + it "sets the repository to the right path after a rename" do + new_path = 'renamed_path' + project_params = { path: new_path } + controller.instance_variable_set(:@project, project) + sign_in(admin) + + put :update, + namespace_id: project.namespace.to_param, + id: project.id, + project: project_params + + expect(project.repository.path).to include(new_path) + expect(assigns(:repository).path).to eq(project.repository.path) + expect(response.status).to eq(200) + end + end + describe "#destroy" do let(:admin) { create(:admin) } diff --git a/spec/controllers/root_controller_spec.rb b/spec/controllers/root_controller_spec.rb index 5a104ae7c99..b14d275f7fa 100644 --- a/spec/controllers/root_controller_spec.rb +++ b/spec/controllers/root_controller_spec.rb @@ -43,6 +43,28 @@ describe RootController do end end + context 'who has customized their dashboard setting for groups' do + before do + user.update_attribute(:dashboard, 'groups') + end + + it 'redirects to their group list' do + get :index + expect(response).to redirect_to dashboard_groups_path + end + end + + context 'who has customized their dashboard setting for todos' do + before do + user.update_attribute(:dashboard, 'todos') + end + + it 'redirects to their todo list' do + get :index + expect(response).to redirect_to dashboard_todos_path + end + end + context 'who uses the default dashboard setting' do it 'renders the default dashboard' do get :index diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb new file mode 100644 index 00000000000..83cc8ec6d26 --- /dev/null +++ b/spec/controllers/sessions_controller_spec.rb @@ -0,0 +1,101 @@ +require 'spec_helper' + +describe SessionsController do + describe '#create' do + before do + @request.env['devise.mapping'] = Devise.mappings[:user] + end + + context 'when using standard authentications' do + context 'invalid password' do + it 'does not authenticate user' do + post(:create, user: { login: 'invalid', password: 'invalid' }) + + expect(response) + .to set_flash.now[:alert].to /Invalid login or password/ + end + end + + context 'when using valid password' do + let(:user) { create(:user) } + + it 'authenticates user correctly' do + post(:create, user: { login: user.username, password: user.password }) + + expect(response).to set_flash.to /Signed in successfully/ + expect(subject.current_user). to eq user + end + end + end + + context 'when using two-factor authentication' do + let(:user) { create(:user, :two_factor) } + + def authenticate_2fa(user_params) + post(:create, { user: user_params }, { otp_user_id: user.id }) + end + + ## + # See #14900 issue + # + context 'when authenticating with login and OTP of another user' do + context 'when another user has 2FA enabled' do + let(:another_user) { create(:user, :two_factor) } + + context 'when OTP is valid for another user' do + it 'does not authenticate' do + authenticate_2fa(login: another_user.username, + otp_attempt: another_user.current_otp) + + expect(subject.current_user).to_not eq another_user + end + end + + context 'when OTP is invalid for another user' do + it 'does not authenticate' do + authenticate_2fa(login: another_user.username, + otp_attempt: 'invalid') + + expect(subject.current_user).to_not eq another_user + end + end + + context 'when authenticating with OTP' do + context 'when OTP is valid' do + it 'authenticates correctly' do + authenticate_2fa(otp_attempt: user.current_otp) + + expect(subject.current_user).to eq user + end + end + + context 'when OTP is invalid' do + before { authenticate_2fa(otp_attempt: 'invalid') } + + it 'does not authenticate' do + expect(subject.current_user).to_not eq user + end + + it 'warns about invalid OTP code' do + expect(response).to set_flash.now[:alert] + .to /Invalid two-factor code/ + end + end + end + + context 'when another user does not have 2FA enabled' do + let(:another_user) { create(:user) } + + it 'does not leak that 2FA is disabled for another user' do + authenticate_2fa(login: another_user.username, + otp_attempt: 'invalid') + + expect(response).to set_flash.now[:alert] + .to /Invalid two-factor code/ + end + end + end + end + end + end +end diff --git a/spec/factories/file_uploader.rb b/spec/factories/file_uploader.rb new file mode 100644 index 00000000000..1b36e21f2b0 --- /dev/null +++ b/spec/factories/file_uploader.rb @@ -0,0 +1,20 @@ +FactoryGirl.define do + factory :file_uploader do + project + secret nil + + transient do + fixture { 'rails_sample.jpg' } + path { File.join(Rails.root, 'spec/fixtures', fixture) } + file { Rack::Test::UploadedFile.new(path) } + end + + after(:build) do |uploader, evaluator| + uploader.store!(evaluator.file) + end + + initialize_with do + new(project, secret) + end + end +end diff --git a/spec/factories/forked_project_links.rb b/spec/factories/forked_project_links.rb index 252bf2747e1..19a54946fe0 100644 --- a/spec/factories/forked_project_links.rb +++ b/spec/factories/forked_project_links.rb @@ -13,5 +13,10 @@ FactoryGirl.define do factory :forked_project_link do association :forked_to_project, factory: :project association :forked_from_project, factory: :project + + after(:create) do |link| + link.forked_from_project.reload + link.forked_to_project.reload + end end end diff --git a/spec/factories_spec.rb b/spec/factories_spec.rb index 457859dedaf..62de081661d 100644 --- a/spec/factories_spec.rb +++ b/spec/factories_spec.rb @@ -1,9 +1,17 @@ require 'spec_helper' -FactoryGirl.factories.map(&:name).each do |factory_name| - describe "#{factory_name} factory" do - it 'should be valid' do - expect(build(factory_name)).to be_valid +describe 'factories' do + FactoryGirl.factories.each do |factory| + describe "#{factory.name} factory" do + let(:entity) { build(factory.name) } + + it 'does not raise error when created 'do + expect { entity }.to_not raise_error + end + + it 'should be valid', if: factory.build_class < ActiveRecord::Base do + expect(entity).to be_valid + end end end end diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb index dc41be8246f..de6aed74fb4 100644 --- a/spec/features/atom/users_spec.rb +++ b/spec/features/atom/users_spec.rb @@ -61,7 +61,7 @@ describe "User Feed", feature: true do end it 'should have XHTML summaries in merge request descriptions' do - expect(body).to match /Here is the fix: <img[^>]*\/>/ + expect(body).to match /Here is the fix: <a[^>]*><img[^>]*\/><\/a>/ end end end diff --git a/spec/features/dashboard_issues_spec.rb b/spec/features/dashboard_issues_spec.rb new file mode 100644 index 00000000000..39805da9d0b --- /dev/null +++ b/spec/features/dashboard_issues_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe "Dashboard Issues filtering", feature: true, js: true do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:milestone) { create(:milestone, project: project) } + + context 'filtering by milestone' do + before do + project.team << [user, :master] + login_as(user) + + create(:issue, project: project, author: user, assignee: user) + create(:issue, project: project, author: user, assignee: user, milestone: milestone) + + visit_issues + end + + it 'should show all issues with no milestone' do + show_milestone_dropdown + + click_link 'No Milestone' + + expect(page).to have_selector('.issue', count: 1) + end + + it 'should show all issues with any milestone' do + show_milestone_dropdown + + click_link 'Any Milestone' + + expect(page).to have_selector('.issue', count: 2) + end + + it 'should show all issues with the selected milestone' do + show_milestone_dropdown + + page.within '.dropdown-content' do + click_link milestone.title + end + + expect(page).to have_selector('.issue', count: 1) + end + end + + def show_milestone_dropdown + click_button 'Milestone' + expect(page).to have_selector('.dropdown-content', visible: true) + end + + def visit_issues + visit issues_dashboard_path + end +end diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb new file mode 100644 index 00000000000..41af789aae2 --- /dev/null +++ b/spec/features/issues/award_emoji_spec.rb @@ -0,0 +1,64 @@ +require 'rails_helper' + +describe 'Awards Emoji', feature: true do + let!(:project) { create(:project) } + let!(:user) { create(:user) } + + before do + project.team << [user, :master] + login_as(user) + end + + describe 'Click award emoji from issue#show' do + let!(:issue) do + create(:issue, + author: @user, + assignee: @user, + project: project) + end + + before do + visit namespace_project_issue_path(project.namespace, project, issue) + end + + it 'should increment the thumbsdown emoji', js: true do + find('[data-emoji="thumbsdown"]').click + sleep 2 + expect(thumbsdown_emoji).to have_text("1") + end + + context 'click the thumbsup emoji' do + + it 'should increment the thumbsup emoji', js: true do + find('[data-emoji="thumbsup"]').click + sleep 2 + expect(thumbsup_emoji).to have_text("1") + end + + it 'should decrement the thumbsdown emoji', js: true do + expect(thumbsdown_emoji).to have_text("0") + end + end + + context 'click the thumbsdown emoji' do + + it 'should increment the thumbsdown emoji', js: true do + find('[data-emoji="thumbsdown"]').click + sleep 2 + expect(thumbsdown_emoji).to have_text("1") + end + + it 'should decrement the thumbsup emoji', js: true do + expect(thumbsup_emoji).to have_text("0") + end + end + end + + def thumbsup_emoji + page.all('span.js-counter').first + end + + def thumbsdown_emoji + page.all('span.js-counter').last + end +end diff --git a/spec/features/issues/filter_by_milestone_spec.rb b/spec/features/issues/filter_by_milestone_spec.rb index f6e33f651c4..99445185893 100644 --- a/spec/features/issues/filter_by_milestone_spec.rb +++ b/spec/features/issues/filter_by_milestone_spec.rb @@ -11,7 +11,41 @@ feature 'Issue filtering by Milestone', feature: true do visit_issues(project) filter_by_milestone(Milestone::None.title) - expect(page).to have_css('.issue .title', count: 1) + expect(page).to have_css('.issue', count: 1) + end + + context 'filters by upcoming milestone', js: true do + it 'should not show issues with no expiry' do + create(:issue, project: project) + create(:issue, project: project, milestone: milestone) + + visit_issues(project) + filter_by_milestone(Milestone::Upcoming.title) + + expect(page).to have_css('.issue', count: 0) + end + + it 'should show issues in future' do + milestone = create(:milestone, project: project, due_date: Date.tomorrow) + create(:issue, project: project) + create(:issue, project: project, milestone: milestone) + + visit_issues(project) + filter_by_milestone(Milestone::Upcoming.title) + + expect(page).to have_css('.issue', count: 1) + end + + it 'should not show issues in past' do + milestone = create(:milestone, project: project, due_date: Date.yesterday) + create(:issue, project: project) + create(:issue, project: project, milestone: milestone) + + visit_issues(project) + filter_by_milestone(Milestone::Upcoming.title) + + expect(page).to have_css('.issue', count: 0) + end end scenario 'filters by a specific Milestone', js: true do @@ -21,7 +55,7 @@ feature 'Issue filtering by Milestone', feature: true do visit_issues(project) filter_by_milestone(milestone.title) - expect(page).to have_css('.issue .title', count: 1) + expect(page).to have_css('.issue', count: 1) end def visit_issues(project) @@ -30,8 +64,6 @@ feature 'Issue filtering by Milestone', feature: true do def filter_by_milestone(title) find(".js-milestone-select").click - sleep 0.5 - find(".milestone-filter a", text: title).click - sleep 1 + find(".milestone-filter .dropdown-content a", text: title).click end end diff --git a/spec/features/issues/filter_issues_spec.rb b/spec/features/issues/filter_issues_spec.rb new file mode 100644 index 00000000000..90822a8c123 --- /dev/null +++ b/spec/features/issues/filter_issues_spec.rb @@ -0,0 +1,119 @@ +require 'rails_helper' + +describe 'Filter issues', feature: true do + + let!(:project) { create(:project) } + let!(:user) { create(:user)} + let!(:milestone) { create(:milestone, project: project) } + let!(:label) { create(:label, project: project) } + + before do + project.team << [user, :master] + login_as(user) + end + + describe 'Filter issues for assignee from issues#index' do + + before do + visit namespace_project_issues_path(project.namespace, project) + + find('.js-assignee-search').click + + find('.dropdown-menu-user-link', text: user.username).click + + sleep 2 + end + + context 'assignee', js: true do + it 'should update to current user' do + expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name) + end + + it 'should not change when closed link is clicked' do + find('.issues-state-filters a', text: "Closed").click + + expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name) + end + + + it 'should not change when all link is clicked' do + find('.issues-state-filters a', text: "All").click + + expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name) + end + end + end + + describe 'Filter issues for milestone from issues#index' do + + before do + visit namespace_project_issues_path(project.namespace, project) + + find('.js-milestone-select').click + + find('.milestone-filter .dropdown-content a', text: milestone.title).click + + sleep 2 + end + + context 'milestone', js: true do + it 'should update to current milestone' do + expect(find('.js-milestone-select .dropdown-toggle-text')).to have_content(milestone.title) + end + + it 'should not change when closed link is clicked' do + find('.issues-state-filters a', text: "Closed").click + + expect(find('.js-milestone-select .dropdown-toggle-text')).to have_content(milestone.title) + end + + + it 'should not change when all link is clicked' do + find('.issues-state-filters a', text: "All").click + + expect(find('.js-milestone-select .dropdown-toggle-text')).to have_content(milestone.title) + end + end + end + + describe 'Filter issues for assignee and label from issues#index' do + + before do + visit namespace_project_issues_path(project.namespace, project) + + find('.js-assignee-search').click + + find('.dropdown-menu-user-link', text: user.username).click + + sleep 2 + + find('.js-label-select').click + + find('.dropdown-menu-labels .dropdown-content a', text: label.title).click + + sleep 2 + end + + context 'assignee and label', js: true do + it 'should update to current assignee and label' do + expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name) + expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title) + end + + it 'should not change when closed link is clicked' do + find('.issues-state-filters a', text: "Closed").click + + expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name) + expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title) + end + + + it 'should not change when all link is clicked' do + find('.issues-state-filters a', text: "All").click + + expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name) + expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title) + end + end + end +end diff --git a/spec/features/issues/update_issues_spec.rb b/spec/features/issues/update_issues_spec.rb new file mode 100644 index 00000000000..3eb903a93fe --- /dev/null +++ b/spec/features/issues/update_issues_spec.rb @@ -0,0 +1,117 @@ +require 'rails_helper' + +feature 'Multiple issue updating from issues#index', feature: true do + let!(:project) { create(:project) } + let!(:issue) { create(:issue, project: project) } + let!(:user) { create(:user)} + + before do + project.team << [user, :master] + login_as(user) + end + + context 'status', js: true do + it 'should be set to closed' do + visit namespace_project_issues_path(project.namespace, project) + + find('#check_all_issues').click + find('.js-issue-status').click + + find('.dropdown-menu-status a', text: 'Closed').click + click_update_issues_button + expect(page).to have_selector('.issue', count: 0) + end + + it 'should be set to open' do + create_closed + visit namespace_project_issues_path(project.namespace, project) + + find('.issues-state-filters a', text: 'Closed').click + + find('#check_all_issues').click + find('.js-issue-status').click + + find('.dropdown-menu-status a', text: 'Open').click + click_update_issues_button + expect(page).to have_selector('.issue', count: 0) + end + end + + context 'assignee', js: true do + it 'should update to current user' do + visit namespace_project_issues_path(project.namespace, project) + + find('#check_all_issues').click + find('.js-update-assignee').click + + find('.dropdown-menu-user-link', text: user.username).click + click_update_issues_button + + page.within('.issue .controls') do + expect(find('.author_link')["data-original-title"]).to have_content(user.name) + end + end + + it 'should update to unassigned' do + create_assigned + visit namespace_project_issues_path(project.namespace, project) + + find('#check_all_issues').click + find('.js-update-assignee').click + + click_link 'Unassigned' + click_update_issues_button + + within first('.issue .controls') do + expect(page).to have_no_selector('.author_link') + end + end + end + + context 'milestone', js: true do + let(:milestone) { create(:milestone, project: project) } + + it 'should update milestone' do + visit namespace_project_issues_path(project.namespace, project) + + find('#check_all_issues').click + find('.issues_bulk_update .js-milestone-select').click + + find('.dropdown-menu-milestone a', text: milestone.title).click + click_update_issues_button + + expect(find('.issue')).to have_content milestone.title + end + + it 'should set to no milestone' do + create_with_milestone + visit namespace_project_issues_path(project.namespace, project) + + expect(first('.issue')).to have_content milestone.title + + find('#check_all_issues').click + find('.issues_bulk_update .js-milestone-select').click + + find('.dropdown-menu-milestone a', text: "No Milestone").click + click_update_issues_button + + expect(first('.issue')).to_not have_content milestone.title + end + end + + def create_closed + create(:issue, project: project, state: :closed) + end + + def create_assigned + create(:issue, project: project, assignee: user) + end + + def create_with_milestone + create(:issue, project: project, milestone: milestone) + end + + def click_update_issues_button + find('.update_selected_issues').click + end +end diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index e844e681ebf..79000666ccc 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -22,7 +22,7 @@ describe 'Issues', feature: true do before do visit edit_namespace_project_issue_path(project.namespace, project, issue) - click_link "Edit" + click_button "Go full screen" end it 'should open new issue popup' do @@ -34,20 +34,7 @@ describe 'Issues', feature: true do fill_in 'issue_title', with: 'bug 345' fill_in 'issue_description', with: 'bug description' end - - it 'does not change issue count' do - expect { click_button 'Save changes' }.to_not change { Issue.count } - end - - it 'should update issue fields' do - click_button 'Save changes' - - expect(page).to have_content @user.name - expect(page).to have_content 'bug 345' - expect(page).to have_content project.name - end end - end describe 'Editing issue assignee' do @@ -70,7 +57,7 @@ describe 'Issues', feature: true do click_button 'Save changes' page.within('.assignee') do - expect(page).to have_content 'None' + expect(page).to have_content 'No assignee - assign yourself' end expect(issue.reload.assignee).to be_nil @@ -198,20 +185,26 @@ describe 'Issues', feature: true do end describe 'update assignee from issue#show' do - let(:issue) { create(:issue, project: project, author: @user) } + let(:issue) { create(:issue, project: project, author: @user, assignee: @user) } context 'by autorized user' do - it 'with dropdown menu' do + it 'allows user to select unassigned', js: true do visit namespace_project_issue_path(project.namespace, project, issue) - find('.issuable-sidebar #issue_assignee_id'). - set project.team.members.first.id - click_button 'Update Issue' + page.within('.assignee') do + expect(page).to have_content "#{@user.name}" + end + + find('.block.assignee .edit-link').click + sleep 2 # wait for ajax stuff to complete + first('.dropdown-menu-user-link').click + sleep 2 + page.within('.assignee') do + expect(page).to have_content 'No assignee' + end - expect(page).to have_content 'Assignee' - has_select?('issue_assignee_id', - selected: project.team.members.first.name) + expect(issue.reload.assignee).to be_nil end end @@ -221,8 +214,6 @@ describe 'Issues', feature: true do before :each do project.team << [[guest], :guest] - issue.assignee = @user - issue.save end it 'shows assignee text', js: true do @@ -241,20 +232,23 @@ describe 'Issues', feature: true do context 'by authorized user' do - it 'with dropdown menu' do - visit namespace_project_issue_path(project.namespace, project, issue) - find('.issuable-sidebar'). - select(milestone.title, from: 'issue_milestone_id') - click_button 'Update Issue' + it 'allows user to select unassigned', js: true do + visit namespace_project_issue_path(project.namespace, project, issue) - expect(page).to have_content "Milestone changed to #{milestone.title}" + page.within('.milestone') do + expect(page).to have_content "None" + end + find('.block.milestone .edit-link').click + sleep 2 # wait for ajax stuff to complete + first('.dropdown-content li').click + sleep 2 page.within('.milestone') do - expect(page).to have_content milestone.title + expect(page).to have_content 'None' end - has_select?('issue_assignee_id', selected: milestone.title) + expect(issue.reload.milestone).to be_nil end end @@ -283,25 +277,6 @@ describe 'Issues', feature: true do issue.assignee = user2 issue.save end - - it 'allows user to remove assignee', js: true do - visit namespace_project_issue_path(project.namespace, project, issue) - - page.within('.assignee') do - expect(page).to have_content user2.name - end - - find('.assignee .edit-link').click - sleep 2 # wait for ajax stuff to complete - first('.user-result').click - - page.within('.assignee') do - expect(page).to have_content 'None' - end - - sleep 2 # wait for ajax stuff to complete - expect(issue.reload.assignee).to be_nil - end end end diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb index 12fd8d37210..3d0d0e59fd7 100644 --- a/spec/features/markdown_spec.rb +++ b/spec/features/markdown_spec.rb @@ -39,7 +39,7 @@ describe 'GitLab Markdown', feature: true do end def doc(html = @html) - Nokogiri::HTML::DocumentFragment.parse(html) + @doc ||= Nokogiri::HTML::DocumentFragment.parse(html) end # Shared behavior that all pipelines should exhibit @@ -230,6 +230,7 @@ describe 'GitLab Markdown', feature: true do file = Gollum::File.new(@project_wiki.wiki) expect(file).to receive(:path).and_return('images/example.jpg') expect(@project_wiki).to receive(:find_file).with('images/example.jpg').and_return(file) + allow(@project_wiki).to receive(:wiki_base_path) { '/namespace1/gitlabhq/wikis' } @html = markdown(@feat.raw_markdown, { pipeline: :wiki, project_wiki: @project_wiki }) end diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb new file mode 100644 index 00000000000..00b60bd0e75 --- /dev/null +++ b/spec/features/merge_requests/create_new_mr_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +feature 'Create New Merge Request', feature: true, js: true do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + + before do + project.team << [user, :master] + + login_as user + visit namespace_project_merge_requests_path(project.namespace, project) + end + + it 'generates a diff for an orphaned branch' do + click_link 'New Merge Request' + + first('.js-source-branch').click + first('.dropdown-source-branch .dropdown-content a', text: 'orphaned-branch').click + + click_button "Compare branches" + click_link "Changes" + + expect(page).to have_content "README.md" + expect(page).to have_content "wm.png" + + fill_in "merge_request_title", with: "Orphaned MR test" + click_button "Submit merge request" + + click_link "Check out branch" + + expect(page).to have_content 'git checkout -b orphaned-branch origin/orphaned-branch' + end +end diff --git a/spec/features/merge_requests/filter_by_milestone_spec.rb b/spec/features/merge_requests/filter_by_milestone_spec.rb index b76e4c74c79..c57ab5f3b03 100644 --- a/spec/features/merge_requests/filter_by_milestone_spec.rb +++ b/spec/features/merge_requests/filter_by_milestone_spec.rb @@ -11,7 +11,41 @@ feature 'Merge Request filtering by Milestone', feature: true do visit_merge_requests(project) filter_by_milestone(Milestone::None.title) - expect(page).to have_css('.merge-request-title', count: 1) + expect(page).to have_css('.merge-request', count: 1) + end + + context 'filters by upcoming milestone', js: true do + it 'should not show issues with no expiry' do + create(:merge_request, :with_diffs, source_project: project) + create(:merge_request, :simple, source_project: project, milestone: milestone) + + visit_merge_requests(project) + filter_by_milestone(Milestone::Upcoming.title) + + expect(page).to have_css('.merge-request', count: 0) + end + + it 'should show issues in future' do + milestone = create(:milestone, project: project, due_date: Date.tomorrow) + create(:merge_request, :with_diffs, source_project: project) + create(:merge_request, :simple, source_project: project, milestone: milestone) + + visit_merge_requests(project) + filter_by_milestone(Milestone::Upcoming.title) + + expect(page).to have_css('.merge-request', count: 1) + end + + it 'should not show issues in past' do + milestone = create(:milestone, project: project, due_date: Date.yesterday) + create(:merge_request, :with_diffs, source_project: project) + create(:merge_request, :simple, source_project: project, milestone: milestone) + + visit_merge_requests(project) + filter_by_milestone(Milestone::Upcoming.title) + + expect(page).to have_css('.merge-request', count: 0) + end end scenario 'filters by a specific Milestone', js: true do @@ -21,7 +55,7 @@ feature 'Merge Request filtering by Milestone', feature: true do visit_merge_requests(project) filter_by_milestone(milestone.title) - expect(page).to have_css('.merge-request-title', count: 1) + expect(page).to have_css('.merge-request', count: 1) end def visit_merge_requests(project) diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb index d9a8058efd9..5f855ccc701 100644 --- a/spec/features/notes_on_merge_requests_spec.rb +++ b/spec/features/notes_on_merge_requests_spec.rb @@ -152,7 +152,7 @@ describe 'Comments', feature: true do it 'has .new_note css class' do page.within('.js-temp-notes-holder') do - expect(subject).to have_css('.new_note') + expect(subject).to have_css('.new-note') end end end @@ -210,7 +210,7 @@ describe 'Comments', feature: true do is_expected.to have_content('Another comment on line 10') is_expected.to have_css('.notes_holder') is_expected.to have_css('.notes_holder .note', count: 1) - is_expected.to have_button('Reply') + is_expected.to have_button('Reply...') end end end @@ -225,6 +225,6 @@ describe 'Comments', feature: true do end def click_diff_line(data = line_code) - page.find(%Q{button[data-line-code="#{data}"]}, visible: false).click + execute_script("$('button[data-line-code=\"#{data}\"]').click()") end end diff --git a/spec/features/projects/badges/list_spec.rb b/spec/features/projects/badges/list_spec.rb new file mode 100644 index 00000000000..13c9b95b316 --- /dev/null +++ b/spec/features/projects/badges/list_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +feature 'list of badges' do + include Select2Helper + + background do + user = create(:user) + project = create(:project) + project.team << [user, :master] + login_as(user) + visit edit_namespace_project_path(project.namespace, project) + end + + scenario 'user displays list of badges' do + click_link 'Badges' + + expect(page).to have_content 'build status' + expect(page).to have_content 'Markdown' + expect(page).to have_content 'HTML' + expect(page).to have_css('.highlight', count: 2) + expect(page).to have_xpath("//img[@alt='build status']") + + page.within('.highlight', match: :first) do + expect(page).to have_content 'badges/master/build.svg' + end + end + + scenario 'user changes current ref on badges list page', js: true do + click_link 'Badges' + select2('improve/awesome', from: '#ref') + + expect(page).to have_content 'badges/improve/awesome/build.svg' + end +end diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb index 84c036e59c0..3e6289a46b1 100644 --- a/spec/features/search_spec.rb +++ b/spec/features/search_spec.rb @@ -1,19 +1,46 @@ require 'spec_helper' describe "Search", feature: true do + let(:user) { create(:user) } + let(:project) { create(:project, namespace: user.namespace) } + before do - login_as :user - @project = create(:project, namespace: @user.namespace) - @project.team << [@user, :reporter] + login_with(user) + project.team << [user, :reporter] visit search_path + end - page.within '.search-holder' do - fill_in "search", with: @project.name[0..3] - click_button "Search" + describe 'searching for Projects' do + it 'finds a project' do + page.within '.search-holder' do + fill_in "search", with: project.name[0..3] + click_button "Search" + end + + expect(page).to have_content project.name end end - it "should show project in search results" do - expect(page).to have_content @project.name + context 'search for comments' do + it 'finds a snippet' do + snippet = create(:project_snippet, :private, project: project, author: user, title: 'Some title') + note = create(:note, + noteable: snippet, + author: user, + note: 'Supercalifragilisticexpialidocious', + project: project) + # Must visit project dashboard since global search won't search + # everything (e.g. comments, snippets, etc.) + visit namespace_project_path(project.namespace, project) + + page.within '.search' do + fill_in 'search', with: note.note + click_button 'Go' + end + + click_link 'Comments' + + expect(page).to have_link(snippet.title) + end end end diff --git a/spec/features/security/project/snippet/internal_access_spec.rb b/spec/features/security/project/snippet/internal_access_spec.rb new file mode 100644 index 00000000000..db53a9cec97 --- /dev/null +++ b/spec/features/security/project/snippet/internal_access_spec.rb @@ -0,0 +1,78 @@ +require 'spec_helper' + +describe "Internal Project Snippets Access", feature: true do + include AccessMatchers + + let(:project) { create(:project, :internal) } + + let(:owner) { project.owner } + let(:master) { create(:user) } + let(:developer) { create(:user) } + let(:reporter) { create(:user) } + let(:guest) { create(:user) } + let(:internal_snippet) { create(:project_snippet, :internal, project: project, author: owner) } + let(:private_snippet) { create(:project_snippet, :private, project: project, author: owner) } + + before do + project.team << [master, :master] + project.team << [developer, :developer] + project.team << [reporter, :reporter] + project.team << [guest, :guest] + end + + describe "GET /:project_path/snippets" do + subject { namespace_project_snippets_path(project.namespace, project) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end + + describe "GET /:project_path/snippets/new" do + subject { new_namespace_project_snippet_path(project.namespace, project) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end + + describe "GET /:project_path/snippets/:id for an internal snippet" do + subject { namespace_project_snippet_path(project.namespace, project, internal_snippet) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end + + describe "GET /:project_path/snippets/:id for a private snippet" do + subject { namespace_project_snippet_path(project.namespace, project, private_snippet) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end +end diff --git a/spec/features/security/project/snippet/private_access_spec.rb b/spec/features/security/project/snippet/private_access_spec.rb new file mode 100644 index 00000000000..d23d645c8e5 --- /dev/null +++ b/spec/features/security/project/snippet/private_access_spec.rb @@ -0,0 +1,63 @@ +require 'spec_helper' + +describe "Private Project Snippets Access", feature: true do + include AccessMatchers + + let(:project) { create(:project, :private) } + + let(:owner) { project.owner } + let(:master) { create(:user) } + let(:developer) { create(:user) } + let(:reporter) { create(:user) } + let(:guest) { create(:user) } + let(:private_snippet) { create(:project_snippet, :private, project: project, author: owner) } + + before do + project.team << [master, :master] + project.team << [developer, :developer] + project.team << [reporter, :reporter] + project.team << [guest, :guest] + end + + describe "GET /:project_path/snippets" do + subject { namespace_project_snippets_path(project.namespace, project) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end + + describe "GET /:project_path/snippets/new" do + subject { new_namespace_project_snippet_path(project.namespace, project) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end + + describe "GET /:project_path/snippets/:id for a private snippet" do + subject { namespace_project_snippet_path(project.namespace, project, private_snippet) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end +end diff --git a/spec/features/security/project/snippet/public_access_spec.rb b/spec/features/security/project/snippet/public_access_spec.rb new file mode 100644 index 00000000000..e3665b6116a --- /dev/null +++ b/spec/features/security/project/snippet/public_access_spec.rb @@ -0,0 +1,93 @@ +require 'spec_helper' + +describe "Public Project Snippets Access", feature: true do + include AccessMatchers + + let(:project) { create(:project, :public) } + + let(:owner) { project.owner } + let(:master) { create(:user) } + let(:developer) { create(:user) } + let(:reporter) { create(:user) } + let(:guest) { create(:user) } + let(:public_snippet) { create(:project_snippet, :public, project: project, author: owner) } + let(:internal_snippet) { create(:project_snippet, :internal, project: project, author: owner) } + let(:private_snippet) { create(:project_snippet, :private, project: project, author: owner) } + + before do + project.team << [master, :master] + project.team << [developer, :developer] + project.team << [reporter, :reporter] + project.team << [guest, :guest] + end + + describe "GET /:project_path/snippets" do + subject { namespace_project_snippets_path(project.namespace, project) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :external } + it { is_expected.to be_allowed_for :visitor } + end + + describe "GET /:project_path/snippets/new" do + subject { new_namespace_project_snippet_path(project.namespace, project) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end + + describe "GET /:project_path/snippets/:id for a public snippet" do + subject { namespace_project_snippet_path(project.namespace, project, public_snippet) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :external } + it { is_expected.to be_allowed_for :visitor } + end + + describe "GET /:project_path/snippets/:id for an internal snippet" do + subject { namespace_project_snippet_path(project.namespace, project, internal_snippet) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end + + describe "GET /:project_path/snippets/:id for a private snippet" do + subject { namespace_project_snippet_path(project.namespace, project, private_snippet) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end +end diff --git a/spec/fixtures/emails/reply_without_subaddressing_and_key_inside_references.eml b/spec/fixtures/emails/reply_without_subaddressing_and_key_inside_references.eml new file mode 100644 index 00000000000..39d5cefbc2a --- /dev/null +++ b/spec/fixtures/emails/reply_without_subaddressing_and_key_inside_references.eml @@ -0,0 +1,42 @@ +Return-Path: <jake@adventuretime.ooo> +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <reply@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog <jake@adventuretime.ooo> +To: reply@appmail.adventuretime.ooo +Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com> +In-Reply-To: <issue_1@localhost> +References: <issue_1@localhost> <reply-59d8df8370b7e95c5a49fbf86aeb2c93@localhost> +Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 + +I could not disagree more. I am obviously biased but adventure time is the +greatest show ever created. Everyone should watch it. + +- Jake out + + +On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta +<reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo> wrote: +> +> +> +> eviltrout posted in 'Adventure Time Sux' on Discourse Meta: +> +> --- +> hey guys everyone knows adventure time sucks! +> +> --- +> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3 +> +> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences). +> diff --git a/spec/fixtures/emails/valid_reply.eml b/spec/fixtures/emails/valid_reply.eml index 1e696389954..980e10a8812 100644 --- a/spec/fixtures/emails/valid_reply.eml +++ b/spec/fixtures/emails/valid_reply.eml @@ -7,6 +7,8 @@ Date: Thu, 13 Jun 2013 17:03:48 -0400 From: Jake the Dog <jake@adventuretime.ooo> To: reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com> +In-Reply-To: <issue_1@localhost> +References: <issue_1@localhost> <reply-59d8df8370b7e95c5a49fbf86aeb2c93@localhost> Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' Mime-Version: 1.0 Content-Type: text/plain; @@ -37,4 +39,4 @@ On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta > Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3 > > To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences). ->
\ No newline at end of file +> diff --git a/spec/helpers/form_helper_spec.rb b/spec/helpers/form_helper_spec.rb new file mode 100644 index 00000000000..b20373a96fb --- /dev/null +++ b/spec/helpers/form_helper_spec.rb @@ -0,0 +1,46 @@ +require 'rails_helper' + +describe FormHelper do + describe 'form_errors' do + it 'returns nil when model has no errors' do + model = double(errors: []) + + expect(helper.form_errors(model)).to be_nil + end + + it 'renders an alert div' do + model = double(errors: errors_stub('Error 1')) + + expect(helper.form_errors(model)). + to include('<div class="alert alert-danger" id="error_explanation">') + end + + it 'contains a summary message' do + single_error = double(errors: errors_stub('A')) + multi_errors = double(errors: errors_stub('A', 'B', 'C')) + + expect(helper.form_errors(single_error)). + to include('<h4>The form contains the following error:') + expect(helper.form_errors(multi_errors)). + to include('<h4>The form contains the following errors:') + end + + it 'renders each message' do + model = double(errors: errors_stub('Error 1', 'Error 2', 'Error 3')) + + errors = helper.form_errors(model) + + aggregate_failures do + expect(errors).to include('<li>Error 1</li>') + expect(errors).to include('<li>Error 2</li>') + expect(errors).to include('<li>Error 3</li>') + end + end + + def errors_stub(*messages) + ActiveModel::Errors.new(double).tap do |errors| + messages.each { |msg| errors.add(:base, msg) } + end + end + end +end diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb index 9adcd916ced..13de88e2f21 100644 --- a/spec/helpers/gitlab_markdown_helper_spec.rb +++ b/spec/helpers/gitlab_markdown_helper_spec.rb @@ -150,13 +150,6 @@ describe GitlabMarkdownHelper do end end - describe 'random_markdown_tip' do - it 'returns a random Markdown tip' do - stub_const("#{described_class}::MARKDOWN_TIPS", ['Random tip']) - expect(random_markdown_tip).to eq 'Random tip' - end - end - describe '#first_line_in_markdown' do let(:text) { "@#{user.username}, can you look at this?\nHello world\n"} diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index ffd8ebae029..543593cf389 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -80,7 +80,7 @@ describe IssuesHelper do end end - describe '#url_for_new_issue' do + describe 'url_for_new_issue' do let(:issues_url) { ext_project.external_issue_tracker.new_issue_url } let(:ext_expected) do issues_url.gsub(':project_id', ext_project.id.to_s) @@ -117,7 +117,7 @@ describe IssuesHelper do end end - describe "#merge_requests_sentence" do + describe "merge_requests_sentence" do subject { merge_requests_sentence(merge_requests)} let(:merge_requests) do [ build(:merge_request, iid: 1), build(:merge_request, iid: 2), @@ -127,7 +127,7 @@ describe IssuesHelper do it { is_expected.to eq("!1, !2, or !3") } end - describe "#note_active_class" do + describe "note_active_class" do before do @note = create :note @note1 = create :note @@ -142,10 +142,25 @@ describe IssuesHelper do end end - describe "#awards_sort" do + describe "awards_sort" do it "sorts a hash so thumbsup and thumbsdown are always on top" do data = { "thumbsdown" => "some value", "lifter" => "some value", "thumbsup" => "some value" } expect(awards_sort(data).keys).to eq(["thumbsup", "thumbsdown", "lifter"]) end end + + describe "milestone_options" do + it "gets closed milestone from current issue" do + closed_milestone = create(:closed_milestone, project: project) + milestone1 = create(:milestone, project: project) + milestone2 = create(:milestone, project: project) + issue.update_attributes(milestone_id: closed_milestone.id) + + options = milestone_options(issue) + + expect(options).to have_selector('option[selected]', text: closed_milestone.title) + expect(options).to have_selector('option', text: milestone1.title) + expect(options).to have_selector('option', text: milestone2.title) + end + end end diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb index e5df59c4fba..2f9291afc3f 100644 --- a/spec/helpers/preferences_helper_spec.rb +++ b/spec/helpers/preferences_helper_spec.rb @@ -19,7 +19,9 @@ describe PreferencesHelper do ['Your Projects (default)', 'projects'], ['Starred Projects', 'stars'], ["Your Projects' Activity", 'project_activity'], - ["Starred Projects' Activity", 'starred_project_activity'] + ["Starred Projects' Activity", 'starred_project_activity'], + ["Your Groups", 'groups'], + ["Your Todos", 'todos'] ] end end diff --git a/spec/javascripts/fixtures/zen_mode.html.haml b/spec/javascripts/fixtures/zen_mode.html.haml index 1701652c61e..cb906a7feaa 100644 --- a/spec/javascripts/fixtures/zen_mode.html.haml +++ b/spec/javascripts/fixtures/zen_mode.html.haml @@ -1,4 +1,4 @@ -.zennable +.md-area .zen-backdrop %textarea#note_note.js-gfm-input.markdown-area %a.js-zen-enter(tabindex="-1" href="#") diff --git a/spec/javascripts/issue_spec.js.coffee b/spec/javascripts/issue_spec.js.coffee index 86ba9dd8e96..ea27f36e9b5 100644 --- a/spec/javascripts/issue_spec.js.coffee +++ b/spec/javascripts/issue_spec.js.coffee @@ -29,8 +29,8 @@ describe 'reopen/close issue', -> spyOn(jQuery, 'ajax').and.callFake (req) -> expect(req.type).toBe('PUT') expect(req.url).toBe('http://gitlab.com/issues/6/close') - req.success saved: true - + req.success id: 34 + $btnClose = $('a.btn-close') $btnReopen = $('a.btn-reopen') expect($btnReopen).toBeHidden() @@ -94,7 +94,7 @@ describe 'reopen/close issue', -> spyOn(jQuery, 'ajax').and.callFake (req) -> expect(req.type).toBe('PUT') expect(req.url).toBe('http://gitlab.com/issues/6/reopen') - req.success saved: true + req.success id: 34 $btnClose = $('a.btn-close') $btnReopen = $('a.btn-reopen') diff --git a/spec/lib/award_emoji_spec.rb b/spec/lib/award_emoji_spec.rb new file mode 100644 index 00000000000..330678f7f16 --- /dev/null +++ b/spec/lib/award_emoji_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe AwardEmoji do + describe '.urls' do + subject { AwardEmoji.urls } + + it { is_expected.to be_an_instance_of(Array) } + it { is_expected.to_not be_empty } + + context 'every Hash in the Array' do + it 'has the correct keys and values' do + subject.each do |hash| + expect(hash[:name]).to be_an_instance_of(String) + expect(hash[:path]).to be_an_instance_of(String) + end + end + end + end +end diff --git a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb index 5e23c5c319a..fe2ce092e6b 100644 --- a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb +++ b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb @@ -70,20 +70,22 @@ describe Banzai::Filter::GollumTagsFilter, lib: true do end context 'linking internal resources' do - it "the created link's text will be equal to the resource's text" do + it "the created link's text includes the resource's text and wiki base path" do tag = '[[wiki-slug]]' doc = filter("See #{tag}", project_wiki: project_wiki) + expected_path = ::File.join(project_wiki.wiki_base_path, 'wiki-slug') expect(doc.at_css('a').text).to eq 'wiki-slug' - expect(doc.at_css('a')['href']).to eq 'wiki-slug' + expect(doc.at_css('a')['href']).to eq expected_path end it "the created link's text will be link-text" do tag = '[[link-text|wiki-slug]]' doc = filter("See #{tag}", project_wiki: project_wiki) + expected_path = ::File.join(project_wiki.wiki_base_path, 'wiki-slug') expect(doc.at_css('a').text).to eq 'link-text' - expect(doc.at_css('a')['href']).to eq 'wiki-slug' + expect(doc.at_css('a')['href']).to eq expected_path end end diff --git a/spec/lib/banzai/filter/image_link_filter_spec.rb b/spec/lib/banzai/filter/image_link_filter_spec.rb new file mode 100644 index 00000000000..dd5594750c8 --- /dev/null +++ b/spec/lib/banzai/filter/image_link_filter_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe Banzai::Filter::ImageLinkFilter, lib: true do + include FilterSpecHelper + + def image(path) + %(<img src="#{path}" />) + end + + it 'wraps the image with a link to the image src' do + doc = filter(image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')) + expect(doc.at_css('img')['src']).to eq doc.at_css('a')['href'] + end + + it 'does not wrap a duplicate link' do + exp = act = %q(<a href="/whatever">#{image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')}</a>) + expect(filter(act).to_html).to eq exp + end + + it 'works with external images' do + doc = filter(image('https://i.imgur.com/DfssX9C.jpg')) + expect(doc.at_css('img')['src']).to eq doc.at_css('a')['href'] + end +end diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb index 5a0d3d577a8..266ebef33d6 100644 --- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb @@ -95,6 +95,14 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do result = reference_pipeline_result("Fixed #{reference}") expect(result[:references][:issue]).to eq [issue] end + + it 'does not process links containing issue numbers followed by text' do + href = "#{reference}st" + doc = reference_filter("<a href='#{href}'></a>") + link = doc.css('a').first.attr('href') + + expect(link).to eq(href) + end end context 'cross-project reference' do diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb index 3e25406e498..7aa1b4a3bf6 100644 --- a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb @@ -11,7 +11,7 @@ describe Banzai::Pipeline::WikiPipeline do Foo MD - result = described_class.call(markdown, project: spy, project_wiki: double) + result = described_class.call(markdown, project: spy, project_wiki: spy) aggregate_failures do expect(result[:output].text).not_to include '[[' @@ -29,7 +29,7 @@ describe Banzai::Pipeline::WikiPipeline do Foo MD - output = described_class.to_html(markdown, project: spy, project_wiki: double) + output = described_class.to_html(markdown, project: spy, project_wiki: spy) expect(output).to include('[[<em>toc</em>]]') end @@ -42,7 +42,7 @@ describe Banzai::Pipeline::WikiPipeline do Foo MD - output = described_class.to_html(markdown, project: spy, project_wiki: double) + output = described_class.to_html(markdown, project: spy, project_wiki: spy) aggregate_failures do expect(output).not_to include('<ul>') diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb index f38fadda9ba..566035c60d0 100644 --- a/spec/lib/extracts_path_spec.rb +++ b/spec/lib/extracts_path_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe ExtractsPath, lib: true do include ExtractsPath include RepoHelpers - include Gitlab::Application.routes.url_helpers + include Gitlab::Routing.url_helpers let(:project) { double('project') } diff --git a/spec/lib/gitlab/badge/build_spec.rb b/spec/lib/gitlab/badge/build_spec.rb new file mode 100644 index 00000000000..329792bb685 --- /dev/null +++ b/spec/lib/gitlab/badge/build_spec.rb @@ -0,0 +1,103 @@ +require 'spec_helper' + +describe Gitlab::Badge::Build do + let(:project) { create(:project) } + let(:sha) { project.commit.sha } + let(:branch) { 'master' } + let(:badge) { described_class.new(project, branch) } + + describe '#type' do + subject { badge.type } + it { is_expected.to eq 'image/svg+xml' } + end + + describe '#to_html' do + let(:html) { Nokogiri::HTML.parse(badge.to_html) } + let(:a_href) { html.at('a') } + + it 'points to link' do + expect(a_href[:href]).to eq badge.link_url + end + + it 'contains clickable image' do + expect(a_href.children.first.name).to eq 'img' + end + end + + describe '#to_markdown' do + subject { badge.to_markdown } + + it { is_expected.to include badge.image_url } + it { is_expected.to include badge.link_url } + end + + describe '#image_url' do + subject { badge.image_url } + it { is_expected.to include "badges/#{branch}/build.svg" } + end + + describe '#link_url' do + subject { badge.link_url } + it { is_expected.to include "commits/#{branch}" } + end + + context 'build exists' do + let(:ci_commit) { create(:ci_commit, project: project, sha: sha) } + let!(:build) { create(:ci_build, commit: ci_commit) } + + + context 'build success' do + before { build.success! } + + describe '#to_s' do + subject { badge.to_s } + it { is_expected.to eq 'build-success' } + end + + describe '#data' do + let(:data) { badge.data } + + it 'contains infromation about success' do + expect(status_node(data, 'success')).to be_truthy + end + end + end + + context 'build failed' do + before { build.drop! } + + describe '#to_s' do + subject { badge.to_s } + it { is_expected.to eq 'build-failed' } + end + + describe '#data' do + let(:data) { badge.data } + + it 'contains infromation about failure' do + expect(status_node(data, 'failed')).to be_truthy + end + end + end + end + + context 'build does not exist' do + describe '#to_s' do + subject { badge.to_s } + it { is_expected.to eq 'build-unknown' } + end + + describe '#data' do + let(:data) { badge.data } + + it 'contains infromation about unknown build' do + expect(status_node(data, 'unknown')).to be_truthy + end + end + end + + def status_node(data, status) + xml = Nokogiri::XML.parse(data) + xml.at(%Q{text:contains("#{status}")}) + end +end diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb index 844fd79c991..e9b8ce6b5bb 100644 --- a/spec/lib/gitlab/closing_issue_extractor_spec.rb +++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb @@ -23,11 +23,21 @@ describe Gitlab::ClosingIssueExtractor, lib: true do end it do + message = "Awesome commit (Closes: #{reference})" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do message = "Awesome commit (closes #{reference})" expect(subject.closed_by_message(message)).to eq([issue]) end it do + message = "Awesome commit (closes: #{reference})" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do message = "Closed #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end @@ -38,105 +48,210 @@ describe Gitlab::ClosingIssueExtractor, lib: true do end it do + message = "closed: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do message = "Closing #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do + message = "Closing: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do message = "closing #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do + message = "closing: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do message = "Close #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do + message = "Close: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do message = "close #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do + message = "close: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do message = "Awesome commit (Fixes #{reference})" expect(subject.closed_by_message(message)).to eq([issue]) end it do + message = "Awesome commit (Fixes: #{reference})" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do message = "Awesome commit (fixes #{reference})" expect(subject.closed_by_message(message)).to eq([issue]) end it do + message = "Awesome commit (Fixes: #{reference})" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do message = "Fixed #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do + message = "Fixed: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do message = "fixed #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do + message = "fixed: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do message = "Fixing #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do + message = "Fixing: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do message = "fixing #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do + message = "fixing: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do message = "Fix #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do + message = "Fix: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do message = "fix #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do + message = "fix: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do message = "Awesome commit (Resolves #{reference})" expect(subject.closed_by_message(message)).to eq([issue]) end it do + message = "Awesome commit (Resolves: #{reference})" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do message = "Awesome commit (resolves #{reference})" expect(subject.closed_by_message(message)).to eq([issue]) end it do + message = "Awesome commit (resolves: #{reference})" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do message = "Resolved #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do + message = "Resolved: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do message = "resolved #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do + message = "resolved: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do message = "Resolving #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do + message = "Resolving: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do message = "resolving #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do + message = "resolving: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do message = "Resolve #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do + message = "Resolve: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do message = "resolve #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end + it do + message = "resolve: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + context 'with an external issue tracker reference' do it 'extracts the referenced issue' do jira_project = create(:jira_project, name: 'JIRA_EXT1') @@ -236,6 +351,6 @@ describe Gitlab::ClosingIssueExtractor, lib: true do end def urls - Gitlab::Application.routes.url_helpers + Gitlab::Routing.url_helpers end end diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb index abe179cd4af..36267faeb93 100644 --- a/spec/lib/gitlab/email/receiver_spec.rb +++ b/spec/lib/gitlab/email/receiver_spec.rb @@ -3,6 +3,7 @@ require "spec_helper" describe Gitlab::Email::Receiver, lib: true do before do stub_incoming_email_setting(enabled: true, address: "reply+%{key}@appmail.adventuretime.ooo") + stub_config_setting(host: 'localhost') end let(:reply_key) { "59d8df8370b7e95c5a49fbf86aeb2c93" } @@ -137,5 +138,27 @@ describe Gitlab::Email::Receiver, lib: true do expect(note.note).to include(markdown) end + + context 'when sub-addressing is not supported' do + before do + stub_incoming_email_setting(enabled: true, address: nil) + end + + shared_examples 'an email that contains a reply key' do |header| + it "fetches the reply key from the #{header} header and creates a comment" do + expect { receiver.execute }.to change { noteable.notes.count }.by(1) + note = noteable.notes.last + + expect(note.author).to eq(sent_notification.recipient) + expect(note.note).to include('I could not disagree more.') + end + end + + context 'reply key is in the References header' do + let(:email_raw) { fixture_file('emails/reply_without_subaddressing_and_key_inside_references.eml') } + + it_behaves_like 'an email that contains a reply key', 'References' + end + end end end diff --git a/spec/lib/gitlab/fogbugz_import/client_spec.rb b/spec/lib/gitlab/fogbugz_import/client_spec.rb new file mode 100644 index 00000000000..2dc71be0254 --- /dev/null +++ b/spec/lib/gitlab/fogbugz_import/client_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe Gitlab::FogbugzImport::Client, lib: true do + + let(:client) { described_class.new(uri: '', token: '') } + let(:one_user) { { 'people' => { 'person' => { "ixPerson" => "2", "sFullName" => "James" } } } } + let(:two_users) { { 'people' => { 'person' => [one_user, { "ixPerson" => "3" }] } } } + + it 'retrieves user_map with one user' do + stub_api(one_user) + + expect(client.user_map.count).to eq(1) + end + + it 'retrieves user_map with two users' do + stub_api(two_users) + + expect(client.user_map.count).to eq(2) + end + + def stub_api(users) + allow_any_instance_of(::Fogbugz::Interface).to receive(:command).with(:listPeople).and_return(users) + end +end diff --git a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb new file mode 100644 index 00000000000..eda956e6f0a --- /dev/null +++ b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' + +describe Gitlab::Gfm::UploadsRewriter do + let(:user) { create(:user) } + let(:old_project) { create(:project) } + let(:new_project) { create(:project) } + let(:rewriter) { described_class.new(text, old_project, user) } + + context 'text contains links to uploads' do + let(:image_uploader) do + build(:file_uploader, project: old_project) + end + + let(:zip_uploader) do + build(:file_uploader, project: old_project, + fixture: 'ci_build_artifacts.zip') + end + + let(:text) do + "Text and #{image_uploader.to_markdown} and #{zip_uploader.to_markdown}" + end + + describe '#rewrite' do + let!(:new_text) { rewriter.rewrite(new_project) } + + let(:old_files) { [image_uploader, zip_uploader].map(&:file) } + let(:new_files) do + described_class.new(new_text, new_project, user).files + end + + let(:old_paths) { old_files.map(&:path) } + let(:new_paths) { new_files.map(&:path) } + + it 'rewrites content' do + expect(new_text).to_not eq text + expect(new_text.length).to eq text.length + end + + it 'copies files' do + expect(new_files).to all(exist) + expect(old_paths).to_not match_array new_paths + expect(old_paths).to all(include(old_project.path_with_namespace)) + expect(new_paths).to all(include(new_project.path_with_namespace)) + end + + it 'does not remove old files' do + expect(old_files).to all(exist) + end + + it 'generates a new secret for each file' do + expect(new_paths).to_not include image_uploader.secret + expect(new_paths).to_not include zip_uploader.secret + end + end + + describe '#needs_rewrite?' do + subject { rewriter.needs_rewrite? } + it { is_expected.to eq true } + end + + describe '#files' do + subject { rewriter.files } + it { is_expected.to be_an(Array) } + end + end +end diff --git a/spec/lib/gitlab/incoming_email_spec.rb b/spec/lib/gitlab/incoming_email_spec.rb index bcdba8d4c12..afb3e26f8fb 100644 --- a/spec/lib/gitlab/incoming_email_spec.rb +++ b/spec/lib/gitlab/incoming_email_spec.rb @@ -7,24 +7,8 @@ describe Gitlab::IncomingEmail, lib: true do stub_incoming_email_setting(enabled: true) end - context "when the address is valid" do - before do - stub_incoming_email_setting(address: "replies+%{key}@example.com") - end - - it "returns true" do - expect(described_class.enabled?).to be_truthy - end - end - - context "when the address is invalid" do - before do - stub_incoming_email_setting(address: "replies@example.com") - end - - it "returns false" do - expect(described_class.enabled?).to be_falsey - end + it 'returns true' do + expect(described_class.enabled?).to be_truthy end end @@ -58,4 +42,10 @@ describe Gitlab::IncomingEmail, lib: true do expect(described_class.key_from_address("replies+key@example.com")).to eq("key") end end + + context 'self.key_from_fallback_reply_message_id' do + it 'returns reply key' do + expect(described_class.key_from_fallback_reply_message_id('reply-key@localhost')).to eq('key') + end + end end diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb index 32a19bf344b..f5b66b8156f 100644 --- a/spec/lib/gitlab/ldap/access_spec.rb +++ b/spec/lib/gitlab/ldap/access_spec.rb @@ -33,7 +33,7 @@ describe Gitlab::LDAP::Access, lib: true do it { is_expected.to be_falsey } - it 'should block user in GitLab' do + it 'blocks user in GitLab' do access.allowed? expect(user).to be_blocked expect(user).to be_ldap_blocked @@ -78,6 +78,31 @@ describe Gitlab::LDAP::Access, lib: true do end it { is_expected.to be_truthy } + + context 'when user cannot be found' do + before do + allow(Gitlab::LDAP::Person).to receive(:find_by_dn).and_return(nil) + end + + it { is_expected.to be_falsey } + + it 'blocks user in GitLab' do + access.allowed? + expect(user).to be_blocked + expect(user).to be_ldap_blocked + end + end + + context 'when user was previously ldap_blocked' do + before do + user.ldap_block + end + + it 'unblocks the user if it exists' do + access.allowed? + expect(user).not_to be_blocked + end + end end end end diff --git a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb new file mode 100644 index 00000000000..e01b0b4bd21 --- /dev/null +++ b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb @@ -0,0 +1,71 @@ +require 'spec_helper' + +describe Gitlab::Metrics::Subscribers::RailsCache do + let(:transaction) { Gitlab::Metrics::Transaction.new } + let(:subscriber) { described_class.new } + + let(:event) { double(:event, duration: 15.2) } + + describe '#cache_read' do + it 'increments the cache_read duration' do + expect(subscriber).to receive(:increment). + with(:cache_read_duration, event.duration) + + subscriber.cache_read(event) + end + end + + describe '#cache_write' do + it 'increments the cache_write duration' do + expect(subscriber).to receive(:increment). + with(:cache_write_duration, event.duration) + + subscriber.cache_write(event) + end + end + + describe '#cache_delete' do + it 'increments the cache_delete duration' do + expect(subscriber).to receive(:increment). + with(:cache_delete_duration, event.duration) + + subscriber.cache_delete(event) + end + end + + describe '#cache_exist?' do + it 'increments the cache_exists duration' do + expect(subscriber).to receive(:increment). + with(:cache_exists_duration, event.duration) + + subscriber.cache_exist?(event) + end + end + + describe '#increment' do + context 'without a transaction' do + it 'returns' do + expect(transaction).not_to receive(:increment) + + subscriber.increment(:foo, 15.2) + end + end + + context 'with a transaction' do + before do + allow(subscriber).to receive(:current_transaction). + and_return(transaction) + end + + it 'increments the total and specific cache duration' do + expect(transaction).to receive(:increment). + with(:cache_duration, event.duration) + + expect(transaction).to receive(:increment). + with(:cache_delete_duration, event.duration) + + subscriber.increment(:cache_delete_duration, event.duration) + end + end + end +end diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb index f8c1d956ca1..d6ae54e25e8 100644 --- a/spec/lib/gitlab/metrics/system_spec.rb +++ b/spec/lib/gitlab/metrics/system_spec.rb @@ -26,4 +26,10 @@ describe Gitlab::Metrics::System do end end end + + describe '.cpu_time' do + it 'returns a Fixnum' do + expect(described_class.cpu_time).to be_an_instance_of(Fixnum) + end + end end diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb index 0ec8a6dc5cb..3dee13e27f4 100644 --- a/spec/lib/gitlab/metrics_spec.rb +++ b/spec/lib/gitlab/metrics_spec.rb @@ -13,7 +13,7 @@ describe Gitlab::Metrics do end end - describe '#submit_metrics' do + describe '.submit_metrics' do it 'prepares and writes the metrics to InfluxDB' do connection = double(:connection) pool = double(:pool) @@ -26,7 +26,7 @@ describe Gitlab::Metrics do end end - describe '#prepare_metrics' do + describe '.prepare_metrics' do it 'returns a Hash with the keys as Symbols' do metrics = described_class. prepare_metrics([{ 'values' => {}, 'tags' => {} }]) @@ -51,7 +51,7 @@ describe Gitlab::Metrics do end end - describe '#escape_value' do + describe '.escape_value' do it 'escapes an equals sign' do expect(described_class.escape_value('foo=')).to eq('foo\\=') end @@ -60,4 +60,42 @@ describe Gitlab::Metrics do expect(described_class.escape_value(10)).to eq('10') end end + + describe '.measure' do + context 'without a transaction' do + it 'returns the return value of the block' do + val = Gitlab::Metrics.measure(:foo) { 10 } + + expect(val).to eq(10) + end + end + + context 'with a transaction' do + let(:transaction) { Gitlab::Metrics::Transaction.new } + + before do + allow(Gitlab::Metrics).to receive(:current_transaction). + and_return(transaction) + end + + it 'adds a metric to the current transaction' do + expect(transaction).to receive(:increment). + with('foo_real_time', a_kind_of(Numeric)) + + expect(transaction).to receive(:increment). + with('foo_cpu_time', a_kind_of(Numeric)) + + expect(transaction).to receive(:increment). + with('foo_call_count', 1) + + Gitlab::Metrics.measure(:foo) { 10 } + end + + it 'returns the return value of the block' do + val = Gitlab::Metrics.measure(:foo) { 10 } + + expect(val).to eq(10) + end + end + end end diff --git a/spec/lib/gitlab/saml/user_spec.rb b/spec/lib/gitlab/saml/user_spec.rb index de7cd99d49d..c2a51d9249c 100644 --- a/spec/lib/gitlab/saml/user_spec.rb +++ b/spec/lib/gitlab/saml/user_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::Saml::User, lib: true do let(:gl_user) { saml_user.gl_user } let(:uid) { 'my-uid' } let(:provider) { 'saml' } - let(:auth_hash) { OmniAuth::AuthHash.new(uid: uid, provider: provider, info: info_hash) } + let(:auth_hash) { OmniAuth::AuthHash.new(uid: uid, provider: provider, info: info_hash, extra: { raw_info: OneLogin::RubySaml::Attributes.new({ 'groups' => %w(Developers Freelancers Designers) }) }) } let(:info_hash) do { name: 'John', @@ -23,10 +23,20 @@ describe Gitlab::Saml::User, lib: true do allow(Gitlab::LDAP::Config).to receive_messages(messages) end + def stub_basic_saml_config + allow(Gitlab::Saml::Config).to receive_messages({ options: { name: 'saml', args: {} } }) + end + + def stub_saml_group_config(groups) + allow(Gitlab::Saml::Config).to receive_messages({ options: { name: 'saml', groups_attribute: 'groups', external_groups: groups, args: {} } }) + end + + before { stub_basic_saml_config } + describe 'account exists on server' do before { stub_omniauth_config({ allow_single_sign_on: ['saml'], auto_link_saml_user: true }) } + let!(:existing_user) { create(:user, email: 'john@mail.com', username: 'john') } context 'and should bind with SAML' do - let!(:existing_user) { create(:user, email: 'john@mail.com', username: 'john') } it 'adds the SAML identity to the existing user' do saml_user.save expect(gl_user).to be_valid @@ -36,6 +46,35 @@ describe Gitlab::Saml::User, lib: true do expect(identity.provider).to eql 'saml' end end + + context 'external groups' do + context 'are defined' do + it 'marks the user as external' do + stub_saml_group_config(%w(Freelancers)) + saml_user.save + expect(gl_user).to be_valid + expect(gl_user.external).to be_truthy + end + end + + before { stub_saml_group_config(%w(Interns)) } + context 'are defined but the user does not belong there' do + it 'does not mark the user as external' do + saml_user.save + expect(gl_user).to be_valid + expect(gl_user.external).to be_falsey + end + end + + context 'user was external, now should not be' do + it 'should make user internal' do + existing_user.update_attribute('external', true) + saml_user.save + expect(gl_user).to be_valid + expect(gl_user.external).to be_falsey + end + end + end end describe 'no account exists on server' do @@ -68,6 +107,26 @@ describe Gitlab::Saml::User, lib: true do end end + context 'external groups' do + context 'are defined' do + it 'marks the user as external' do + stub_saml_group_config(%w(Freelancers)) + saml_user.save + expect(gl_user).to be_valid + expect(gl_user.external).to be_truthy + end + end + + context 'are defined but the user does not belong there' do + it 'does not mark the user as external' do + stub_saml_group_config(%w(Interns)) + saml_user.save + expect(gl_user).to be_valid + expect(gl_user.external).to be_falsey + end + end + end + context 'with auto_link_ldap_user disabled (default)' do before { stub_omniauth_config({ auto_link_ldap_user: false, auto_link_saml_user: false, allow_single_sign_on: ['saml'] }) } include_examples 'to verify compliance with allow_single_sign_on' @@ -76,12 +135,6 @@ describe Gitlab::Saml::User, lib: true do context 'with auto_link_ldap_user enabled' do before { stub_omniauth_config({ auto_link_ldap_user: true, auto_link_saml_user: false }) } - context 'and no LDAP provider defined' do - before { stub_ldap_config(providers: []) } - - include_examples 'to verify compliance with allow_single_sign_on' - end - context 'and at least one LDAP provider is defined' do before { stub_ldap_config(providers: %w(ldapmain)) } @@ -89,19 +142,18 @@ describe Gitlab::Saml::User, lib: true do before do allow(ldap_user).to receive(:uid) { uid } allow(ldap_user).to receive(:username) { uid } - allow(ldap_user).to receive(:email) { ['johndoe@example.com','john2@example.com'] } + allow(ldap_user).to receive(:email) { %w(john@mail.com john2@example.com) } allow(ldap_user).to receive(:dn) { 'uid=user1,ou=People,dc=example' } allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user) end context 'and no account for the LDAP user' do - it 'creates a user with dual LDAP and SAML identities' do saml_user.save expect(gl_user).to be_valid expect(gl_user.username).to eql uid - expect(gl_user.email).to eql 'johndoe@example.com' + expect(gl_user.email).to eql 'john@mail.com' expect(gl_user.identities.length).to eql 2 identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } expect(identities_as_hash).to match_array([ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' }, @@ -111,13 +163,13 @@ describe Gitlab::Saml::User, lib: true do end context 'and LDAP user has an account already' do - let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') } - it "adds the omniauth identity to the LDAP account" do + let!(:existing_user) { create(:omniauth_user, email: 'john@mail.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') } + it 'adds the omniauth identity to the LDAP account' do saml_user.save expect(gl_user).to be_valid expect(gl_user.username).to eql 'john' - expect(gl_user.email).to eql 'john@example.com' + expect(gl_user.email).to eql 'john@mail.com' expect(gl_user.identities.length).to eql 2 identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } expect(identities_as_hash).to match_array([ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' }, @@ -126,19 +178,13 @@ describe Gitlab::Saml::User, lib: true do end end end - - context 'and no corresponding LDAP person' do - before { allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(nil) } - - include_examples 'to verify compliance with allow_single_sign_on' - end end end end describe 'blocking' do - before { stub_omniauth_config({ allow_saml_sign_up: true, auto_link_saml_user: true }) } + before { stub_omniauth_config({ allow_single_sign_on: ['saml'], auto_link_saml_user: true }) } context 'signup with SAML only' do context 'dont block on create' do @@ -162,64 +208,6 @@ describe Gitlab::Saml::User, lib: true do end end - context 'signup with linked omniauth and LDAP account' do - before do - stub_omniauth_config(auto_link_ldap_user: true) - allow(ldap_user).to receive(:uid) { uid } - allow(ldap_user).to receive(:username) { uid } - allow(ldap_user).to receive(:email) { ['johndoe@example.com','john2@example.com'] } - allow(ldap_user).to receive(:dn) { 'uid=user1,ou=People,dc=example' } - allow(saml_user).to receive(:ldap_person).and_return(ldap_user) - end - - context "and no account for the LDAP user" do - context 'dont block on create (LDAP)' do - before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) } - - it do - saml_user.save - expect(gl_user).to be_valid - expect(gl_user).not_to be_blocked - end - end - - context 'block on create (LDAP)' do - before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) } - - it do - saml_user.save - expect(gl_user).to be_valid - expect(gl_user).to be_blocked - end - end - end - - context 'and LDAP user has an account already' do - let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') } - - context 'dont block on create (LDAP)' do - before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) } - - it do - saml_user.save - expect(gl_user).to be_valid - expect(gl_user).not_to be_blocked - end - end - - context 'block on create (LDAP)' do - before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) } - - it do - saml_user.save - expect(gl_user).to be_valid - expect(gl_user).not_to be_blocked - end - end - end - end - - context 'sign-in' do before do saml_user.save @@ -245,26 +233,6 @@ describe Gitlab::Saml::User, lib: true do expect(gl_user).not_to be_blocked end end - - context 'dont block on create (LDAP)' do - before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) } - - it do - saml_user.save - expect(gl_user).to be_valid - expect(gl_user).not_to be_blocked - end - end - - context 'block on create (LDAP)' do - before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) } - - it do - saml_user.save - expect(gl_user).to be_valid - expect(gl_user).not_to be_blocked - end - end end end end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 9b47acfe0cd..631b5094f42 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -35,7 +35,9 @@ describe Notify do subject { Notify.new_issue_email(issue.assignee_id, issue.id) } it_behaves_like 'an assignee email' - it_behaves_like 'an email starting a new thread', 'issue' + it_behaves_like 'an email starting a new thread with reply-by-email enabled' do + let(:model) { issue } + end it_behaves_like 'it should show Gmail Actions View Issue link' it_behaves_like 'an unsubscribeable thread' @@ -73,9 +75,11 @@ describe Notify do 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 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { issue } + end it_behaves_like 'it should show Gmail Actions View Issue link' - it_behaves_like "an unsubscribeable thread" + it_behaves_like 'an unsubscribeable thread' it 'is sent as the author' do sender = subject.header[:from].addrs[0] @@ -104,7 +108,9 @@ describe Notify do subject { Notify.relabeled_issue_email(recipient.id, issue.id, %w[foo bar baz], current_user.id) } it_behaves_like 'a multiple recipients email' - it_behaves_like 'an answer to an existing thread', 'issue' + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { issue } + end it_behaves_like 'it should show Gmail Actions View Issue link' it_behaves_like 'a user cannot unsubscribe through footer link' it_behaves_like 'an email with a labels subscriptions link in its footer' @@ -132,7 +138,9 @@ describe Notify do let(:status) { 'closed' } 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 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { issue } + end it_behaves_like 'it should show Gmail Actions View Issue link' it_behaves_like 'an unsubscribeable thread' @@ -163,7 +171,9 @@ describe Notify do let(:new_issue) { create(:issue) } subject { Notify.issue_moved_email(recipient, issue, new_issue, current_user) } - it_behaves_like 'an answer to an existing thread', 'issue' + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { issue } + end it_behaves_like 'it should show Gmail Actions View Issue link' it_behaves_like 'an unsubscribeable thread' @@ -196,9 +206,11 @@ describe Notify do subject { Notify.new_merge_request_email(merge_request.assignee_id, merge_request.id) } it_behaves_like 'an assignee email' - it_behaves_like 'an email starting a new thread', 'merge_request' + it_behaves_like 'an email starting a new thread with reply-by-email enabled' do + let(:model) { merge_request } + end it_behaves_like 'it should show Gmail Actions View Merge request link' - it_behaves_like "an unsubscribeable thread" + it_behaves_like 'an unsubscribeable thread' it 'has the correct subject' do is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/ @@ -216,10 +228,6 @@ describe Notify do is_expected.to have_body_text /#{merge_request.target_branch}/ end - it 'has the correct message-id set' do - is_expected.to have_header 'Message-ID', "<merge_request_#{merge_request.id}@#{Gitlab.config.gitlab.host}>" - end - context 'when enabled email_author_in_body' do before do allow(current_application_settings).to receive(:email_author_in_body).and_return(true) @@ -247,7 +255,9 @@ describe Notify do subject { Notify.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id) } it_behaves_like 'a multiple recipients email' - it_behaves_like 'an answer to an existing thread', 'merge_request' + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { merge_request } + end it_behaves_like 'it should show Gmail Actions View Merge request link' it_behaves_like "an unsubscribeable thread" @@ -278,7 +288,9 @@ describe Notify do subject { Notify.relabeled_merge_request_email(recipient.id, merge_request.id, %w[foo bar baz], current_user.id) } it_behaves_like 'a multiple recipients email' - it_behaves_like 'an answer to an existing thread', 'merge_request' + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { merge_request } + end it_behaves_like 'it should show Gmail Actions View Merge request link' it_behaves_like 'a user cannot unsubscribe through footer link' it_behaves_like 'an email with a labels subscriptions link in its footer' @@ -306,9 +318,11 @@ describe Notify do let(:status) { 'reopened' } 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 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { merge_request } + end it_behaves_like 'it should show Gmail Actions View Merge request link' - it_behaves_like "an unsubscribeable thread" + it_behaves_like 'an unsubscribeable thread' it 'is sent as the author' do sender = subject.header[:from].addrs[0] @@ -337,9 +351,11 @@ describe Notify do subject { Notify.merged_merge_request_email(recipient.id, merge_request.id, merge_author.id) } it_behaves_like 'a multiple recipients email' - it_behaves_like 'an answer to an existing thread', 'merge_request' + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { merge_request } + end it_behaves_like 'it should show Gmail Actions View Merge request link' - it_behaves_like "an unsubscribeable thread" + it_behaves_like 'an unsubscribeable thread' it 'is sent as the merge author' do sender = subject.header[:from].addrs[0] @@ -456,9 +472,11 @@ describe Notify do subject { Notify.note_commit_email(recipient.id, note.id) } it_behaves_like 'a note email' - it_behaves_like 'an answer to an existing thread', 'commit' + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { commit } + end it_behaves_like 'it should show Gmail Actions View Commit link' - it_behaves_like "a user cannot unsubscribe through footer link" + it_behaves_like 'a user cannot unsubscribe through footer link' it 'has the correct subject' do is_expected.to have_subject /#{commit.title} \(#{commit.short_id}\)/ @@ -477,7 +495,9 @@ describe Notify do subject { Notify.note_merge_request_email(recipient.id, note.id) } it_behaves_like 'a note email' - it_behaves_like 'an answer to an existing thread', 'merge_request' + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { merge_request } + end it_behaves_like 'it should show Gmail Actions View Merge request link' it_behaves_like 'an unsubscribeable thread' @@ -498,7 +518,9 @@ describe Notify do subject { Notify.note_issue_email(recipient.id, note.id) } it_behaves_like 'a note email' - it_behaves_like 'an answer to an existing thread', 'issue' + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { issue } + end it_behaves_like 'it should show Gmail Actions View Issue link' it_behaves_like 'an unsubscribeable thread' diff --git a/spec/mailers/shared/notify.rb b/spec/mailers/shared/notify.rb index 6019af544d3..5a85cb501dd 100644 --- a/spec/mailers/shared/notify.rb +++ b/spec/mailers/shared/notify.rb @@ -10,6 +10,13 @@ shared_context 'gitlab email notification' do ActionMailer::Base.deliveries.clear email = recipient.emails.create(email: "notifications@example.com") recipient.update_attribute(:notification_email, email.email) + stub_incoming_email_setting(enabled: true, address: "reply+%{key}@#{Gitlab.config.gitlab.host}") + end +end + +shared_context 'reply-by-email is enabled with incoming address without %{key}' do + before do + stub_incoming_email_setting(enabled: true, address: "reply@#{Gitlab.config.gitlab.host}") end end @@ -46,25 +53,76 @@ shared_examples 'an email with X-GitLab headers containing project details' do end end -shared_examples 'an email starting a new thread' do |message_id_prefix| - include_examples 'an email with X-GitLab headers containing project details' +shared_examples 'a new thread email with reply-by-email enabled' do + let(:regex) { /\A<reply\-(.*)@#{Gitlab.config.gitlab.host}>\Z/ } + + it 'has a Message-ID header' do + is_expected.to have_header 'Message-ID', "<#{model.class.model_name.singular_route_key}_#{model.id}@#{Gitlab.config.gitlab.host}>" + end - it 'has a discussion identifier' do - is_expected.to have_header 'Message-ID', /<#{message_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/ + it 'has a References header' do + is_expected.to have_header 'References', regex end end -shared_examples 'an answer to an existing thread' do |thread_id_prefix| +shared_examples 'a thread answer email with reply-by-email enabled' do include_examples 'an email with X-GitLab headers containing project details' + let(:regex) { /\A<#{model.class.model_name.singular_route_key}_#{model.id}@#{Gitlab.config.gitlab.host}> <reply\-(.*)@#{Gitlab.config.gitlab.host}>\Z/ } + + it 'has a Message-ID header' do + is_expected.to have_header 'Message-ID', /\A<(.*)@#{Gitlab.config.gitlab.host}>\Z/ + end + + it 'has a In-Reply-To header' do + is_expected.to have_header 'In-Reply-To', "<#{model.class.model_name.singular_route_key}_#{model.id}@#{Gitlab.config.gitlab.host}>" + end + + it 'has a References header' do + is_expected.to have_header 'References', regex + end it 'has a subject that begins with Re: ' do is_expected.to have_subject /^Re: / end +end + +shared_examples 'an email starting a new thread with reply-by-email enabled' do + include_examples 'an email with X-GitLab headers containing project details' + include_examples 'a new thread email with reply-by-email enabled' + + context 'when reply-by-email is enabled with incoming address with %{key}' do + it 'has a Reply-To header' do + is_expected.to have_header 'Reply-To', /<reply+(.*)@#{Gitlab.config.gitlab.host}>\Z/ + end + end + + context 'when reply-by-email is enabled with incoming address without %{key}' do + include_context 'reply-by-email is enabled with incoming address without %{key}' + include_examples 'a new thread email with reply-by-email enabled' + + it 'has a Reply-To header' do + is_expected.to have_header 'Reply-To', /<reply@#{Gitlab.config.gitlab.host}>\Z/ + end + end +end + +shared_examples 'an answer to an existing thread with reply-by-email enabled' do + include_examples 'an email with X-GitLab headers containing project details' + include_examples 'a thread answer email with reply-by-email enabled' + + context 'when reply-by-email is enabled with incoming address with %{key}' do + it 'has a Reply-To header' do + is_expected.to have_header 'Reply-To', /<reply+(.*)@#{Gitlab.config.gitlab.host}>\Z/ + end + end + + context 'when reply-by-email is enabled with incoming address without %{key}' do + include_context 'reply-by-email is enabled with incoming address without %{key}' + include_examples 'a thread answer email with reply-by-email enabled' - it 'has headers that reference an existing thread' do - is_expected.to have_header 'Message-ID', /<(.*)@#{Gitlab.config.gitlab.host}>/ - is_expected.to have_header 'References', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/ - is_expected.to have_header 'In-Reply-To', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/ + it 'has a Reply-To header' do + is_expected.to have_header 'Reply-To', /<reply@#{Gitlab.config.gitlab.host}>\Z/ + end end end @@ -83,10 +141,12 @@ shared_examples 'a new user email' do end shared_examples 'it should have Gmail Actions links' do + it { is_expected.to have_body_text '<script type="application/ld+json">' } 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 '<script type="application/ld+json">' } it { is_expected.to_not have_body_text /ViewAction/ } end diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index b1764d7ac09..520cf1b75de 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -12,7 +12,6 @@ # updated_at :datetime # home_page_url :string(255) # default_branch_protection :integer default(2) -# twitter_sharing_enabled :boolean default(TRUE) # restricted_visibility_levels :text # version_check_enabled :boolean default(TRUE) # max_attachment_size :integer default(10), not null diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index be29b6d66ff..b16ccc6e305 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -9,6 +9,7 @@ describe Issue, "Issuable" do it { is_expected.to belong_to(:author) } it { is_expected.to belong_to(:assignee) } it { is_expected.to have_many(:notes).dependent(:destroy) } + it { is_expected.to have_many(:todos).dependent(:destroy) } end describe "Validation" do diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 5fe44246738..89909c2bcd7 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -59,44 +59,70 @@ describe Event, models: true do end it { expect(@event.push?).to be_truthy } - it { expect(@event.proper?).to be_truthy } + it { expect(@event.visible_to_user?).to be_truthy } it { expect(@event.tag?).to be_falsey } it { expect(@event.branch_name).to eq("master") } it { expect(@event.author).to eq(@user) } end - describe '#proper?' do - context 'issue event' do - let(:project) { create(:empty_project, :public) } - let(:non_member) { create(:user) } - let(:member) { create(:user) } - let(:author) { create(:author) } - let(:assignee) { create(:user) } - let(:admin) { create(:admin) } - let(:event) { Event.new(project: project, action: Event::CREATED, target: issue, author_id: author.id) } - - before do - project.team << [member, :developer] - end + describe '#visible_to_user?' do + let(:project) { create(:empty_project, :public) } + let(:non_member) { create(:user) } + let(:member) { create(:user) } + let(:author) { create(:author) } + let(:assignee) { create(:user) } + let(:admin) { create(:admin) } + let(:issue) { create(:issue, project: project, author: author, assignee: assignee) } + let(:confidential_issue) { create(:issue, :confidential, project: project, author: author, assignee: assignee) } + let(:note_on_issue) { create(:note_on_issue, noteable: issue, project: project) } + let(:note_on_confidential_issue) { create(:note_on_issue, noteable: confidential_issue, project: project) } + let(:event) { Event.new(project: project, target: target, author_id: author.id) } + before do + project.team << [member, :developer] + end + + context 'issue event' do context 'for non confidential issues' do - let(:issue) { create(:issue, project: project, author: author, assignee: assignee) } + let(:target) { issue } - it { expect(event.proper?(non_member)).to eq true } - it { expect(event.proper?(author)).to eq true } - it { expect(event.proper?(assignee)).to eq true } - it { expect(event.proper?(member)).to eq true } - it { expect(event.proper?(admin)).to eq true } + it { expect(event.visible_to_user?(non_member)).to eq true } + it { expect(event.visible_to_user?(author)).to eq true } + it { expect(event.visible_to_user?(assignee)).to eq true } + it { expect(event.visible_to_user?(member)).to eq true } + it { expect(event.visible_to_user?(admin)).to eq true } end context 'for confidential issues' do - let(:issue) { create(:issue, :confidential, project: project, author: author, assignee: assignee) } + let(:target) { confidential_issue } + + it { expect(event.visible_to_user?(non_member)).to eq false } + it { expect(event.visible_to_user?(author)).to eq true } + it { expect(event.visible_to_user?(assignee)).to eq true } + it { expect(event.visible_to_user?(member)).to eq true } + it { expect(event.visible_to_user?(admin)).to eq true } + end + end + + context 'note event' do + context 'on non confidential issues' do + let(:target) { note_on_issue } + + it { expect(event.visible_to_user?(non_member)).to eq true } + it { expect(event.visible_to_user?(author)).to eq true } + it { expect(event.visible_to_user?(assignee)).to eq true } + it { expect(event.visible_to_user?(member)).to eq true } + it { expect(event.visible_to_user?(admin)).to eq true } + end + + context 'on confidential issues' do + let(:target) { note_on_confidential_issue } - it { expect(event.proper?(non_member)).to eq false } - it { expect(event.proper?(author)).to eq true } - it { expect(event.proper?(assignee)).to eq true } - it { expect(event.proper?(member)).to eq true } - it { expect(event.proper?(admin)).to eq true } + it { expect(event.visible_to_user?(non_member)).to eq false } + it { expect(event.visible_to_user?(author)).to eq true } + it { expect(event.visible_to_user?(assignee)).to eq true } + it { expect(event.visible_to_user?(member)).to eq true } + it { expect(event.visible_to_user?(admin)).to eq true } end end end diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb index fd1513cab1b..56a9fbe9720 100644 --- a/spec/models/hooks/system_hook_spec.rb +++ b/spec/models/hooks/system_hook_spec.rb @@ -20,24 +20,27 @@ require "spec_helper" describe SystemHook, models: true do describe "execute" do - before(:each) do - @system_hook = create(:system_hook) - WebMock.stub_request(:post, @system_hook.url) + let(:system_hook) { create(:system_hook) } + let(:user) { create(:user) } + let(:project) { create(:project, namespace: user.namespace) } + let(:group) { create(:group) } + + before do + WebMock.stub_request(:post, system_hook.url) end it "project_create hook" do - Projects::CreateService.new(create(:user), name: 'empty').execute - expect(WebMock).to have_requested(:post, @system_hook.url).with( + Projects::CreateService.new(user, name: 'empty').execute + expect(WebMock).to have_requested(:post, system_hook.url).with( body: /project_create/, headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' } ).once end it "project_destroy hook" do - user = create(:user) - project = create(:empty_project, namespace: user.namespace) Projects::DestroyService.new(project, user, {}).pending_delete! - expect(WebMock).to have_requested(:post, @system_hook.url).with( + + expect(WebMock).to have_requested(:post, system_hook.url).with( body: /project_destroy/, headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' } ).once @@ -45,37 +48,36 @@ describe SystemHook, models: true do it "user_create hook" do create(:user) - expect(WebMock).to have_requested(:post, @system_hook.url).with( + + expect(WebMock).to have_requested(:post, system_hook.url).with( body: /user_create/, headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' } ).once end it "user_destroy hook" do - user = create(:user) user.destroy - expect(WebMock).to have_requested(:post, @system_hook.url).with( + + expect(WebMock).to have_requested(:post, system_hook.url).with( body: /user_destroy/, headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' } ).once end it "project_create hook" do - user = create(:user) - project = create(:project) project.team << [user, :master] - expect(WebMock).to have_requested(:post, @system_hook.url).with( + + expect(WebMock).to have_requested(:post, system_hook.url).with( body: /user_add_to_team/, headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' } ).once end it "project_destroy hook" do - user = create(:user) - project = create(:project) project.team << [user, :master] project.project_members.destroy_all - expect(WebMock).to have_requested(:post, @system_hook.url).with( + + expect(WebMock).to have_requested(:post, system_hook.url).with( body: /user_remove_from_team/, headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' } ).once @@ -83,41 +85,39 @@ describe SystemHook, models: true do it 'group create hook' do create(:group) - expect(WebMock).to have_requested(:post, @system_hook.url).with( + + expect(WebMock).to have_requested(:post, system_hook.url).with( body: /group_create/, headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' } ).once end it 'group destroy hook' do - group = create(:group) group.destroy - expect(WebMock).to have_requested(:post, @system_hook.url).with( + + expect(WebMock).to have_requested(:post, system_hook.url).with( body: /group_destroy/, headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' } ).once end it 'group member create hook' do - group = create(:group) - user = create(:user) group.add_master(user) - expect(WebMock).to have_requested(:post, @system_hook.url).with( + + expect(WebMock).to have_requested(:post, system_hook.url).with( body: /user_add_to_group/, headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' } ).once end it 'group member destroy hook' do - group = create(:group) - user = create(:user) group.add_master(user) group.group_members.destroy_all - expect(WebMock).to have_requested(:post, @system_hook.url).with( + + expect(WebMock).to have_requested(:post, system_hook.url).with( body: /user_remove_from_group/, headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' } ).once end - end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index bd0a4ebe337..6f5d912fe5d 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -224,22 +224,22 @@ describe MergeRequest, models: true do ['WIP ', 'WIP:', 'WIP: ', '[WIP]', '[WIP] ', ' [WIP] WIP [WIP] WIP: WIP '].each do |wip_prefix| it "detects the '#{wip_prefix}' prefix" do subject.title = "#{wip_prefix}#{subject.title}" - expect(subject).to be_work_in_progress + expect(subject.work_in_progress?).to eq true end end it "doesn't detect WIP for words starting with WIP" do subject.title = "Wipwap #{subject.title}" - expect(subject).not_to be_work_in_progress + expect(subject.work_in_progress?).to eq false end it "doesn't detect WIP for words containing with WIP" do subject.title = "WupWipwap #{subject.title}" - expect(subject).not_to be_work_in_progress + expect(subject.work_in_progress?).to eq false end it "doesn't detect WIP by default" do - expect(subject).not_to be_work_in_progress + expect(subject.work_in_progress?).to eq false end end diff --git a/spec/models/project_services/builds_email_service_spec.rb b/spec/models/project_services/builds_email_service_spec.rb index 905379a64e3..2ccbff553f0 100644 --- a/spec/models/project_services/builds_email_service_spec.rb +++ b/spec/models/project_services/builds_email_service_spec.rb @@ -6,18 +6,38 @@ describe BuildsEmailService do let(:service) { BuildsEmailService.new } describe :execute do - it "sends email" do + it 'sends email' do service.recipients = 'test@gitlab.com' data[:build_status] = 'failed' expect(BuildEmailWorker).to receive(:perform_async) service.execute(data) end - it "does not sends email with failed build and allowed_failure on" do + it 'does not send email with succeeded build and notify_only_broken_builds on' do + expect(service).to receive(:notify_only_broken_builds).and_return(true) + data[:build_status] = 'success' + expect(BuildEmailWorker).not_to receive(:perform_async) + service.execute(data) + end + + it 'does not send email with failed build and build_allow_failure is true' do data[:build_status] = 'failed' data[:build_allow_failure] = true expect(BuildEmailWorker).not_to receive(:perform_async) service.execute(data) end + + it 'does not send email with unknown build status' do + data[:build_status] = 'foo' + expect(BuildEmailWorker).not_to receive(:perform_async) + service.execute(data) + end + + it 'does not send email when recipients list is empty' do + service.recipients = ' ,, ' + data[:build_status] = 'failed' + expect(BuildEmailWorker).not_to receive(:perform_async) + service.execute(data) + end end end 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 97e6f03e308..f648cbe2dee 100644 --- a/spec/models/project_services/slack_service/issue_message_spec.rb +++ b/spec/models/project_services/slack_service/issue_message_spec.rb @@ -27,6 +27,16 @@ describe SlackService::IssueMessage, models: true do let(:color) { '#345' } + context '#initialize' do + before do + args[:object_attributes][:description] = nil + end + + it 'returns a non-null description' do + expect(subject.description).to eq('') + end + end + context 'open' do it 'returns a message regarding opening of issues' do expect(subject.pretext).to eq( diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 20f06f4b7e1..f29c389e094 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -104,6 +104,15 @@ describe Project, models: true do end end + describe 'default_scope' do + it 'excludes projects pending deletion from the results' do + project = create(:empty_project) + create(:empty_project, pending_delete: true) + + expect(Project.all).to eq [project] + end + end + describe 'project token' do it 'should set an random token if none provided' do project = FactoryGirl.create :empty_project, runners_token: '' @@ -422,6 +431,12 @@ describe Project, models: true do it { should eq "http://localhost#{avatar_path}" } end + + context 'when git repo is empty' do + let(:project) { create(:empty_project) } + + it { should eq nil } + end end describe :ci_commit do diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 7eac70ae948..4e49c413f23 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe Repository, models: true do include RepoHelpers + TestBlob = Struct.new(:name) let(:repository) { create(:project).repository } let(:user) { create(:user) } @@ -131,7 +132,6 @@ describe Repository, models: true do describe "#license" do before do repository.send(:cache).expire(:license) - TestBlob = Struct.new(:name) end it 'test selection preference' do @@ -148,6 +148,25 @@ describe Repository, models: true do end end + describe "#gitlab_ci_yml" do + it 'returns valid file' do + files = [TestBlob.new('file'), TestBlob.new('.gitlab-ci.yml'), TestBlob.new('copying')] + expect(repository.tree).to receive(:blobs).and_return(files) + + expect(repository.gitlab_ci_yml.name).to eq('.gitlab-ci.yml') + end + + it 'returns nil if not exists' do + expect(repository.tree).to receive(:blobs).and_return([]) + expect(repository.gitlab_ci_yml).to be_nil + end + + it 'returns nil for empty repository' do + expect(repository).to receive(:empty?).and_return(true) + expect(repository.gitlab_ci_yml).to be_nil + end + end + describe :add_branch do context 'when pre hooks were successful' do it 'should run without errors' do @@ -284,7 +303,7 @@ describe Repository, models: true do describe 'when there are no branches' do before do - allow(repository.raw_repository).to receive(:branch_count).and_return(0) + allow(repository).to receive(:branch_count).and_return(0) end it { is_expected.to eq(false) } @@ -292,13 +311,13 @@ describe Repository, models: true do describe 'when there are branches' do it 'returns true' do - expect(repository.raw_repository).to receive(:branch_count).and_return(3) + expect(repository).to receive(:branch_count).and_return(3) expect(subject).to eq(true) end it 'caches the output' do - expect(repository.raw_repository).to receive(:branch_count). + expect(repository).to receive(:branch_count). once. and_return(3) @@ -417,7 +436,7 @@ describe Repository, models: true do it 'expires the visible content cache' do repository.has_visible_content? - expect(repository.raw_repository).to receive(:branch_count). + expect(repository).to receive(:branch_count). once. and_return(0) @@ -539,7 +558,7 @@ describe Repository, models: true do end it 'flushes the exists cache' do - expect(repository).to receive(:expire_exists_cache) + expect(repository).to receive(:expire_exists_cache).twice repository.before_delete end @@ -593,6 +612,20 @@ describe Repository, models: true do end end + describe '#before_import' do + it 'flushes the emptiness cachess' do + expect(repository).to receive(:expire_emptiness_caches) + + repository.before_import + end + + it 'flushes the exists cache' do + expect(repository).to receive(:expire_exists_cache) + + repository.before_import + end + end + describe '#after_import' do it 'flushes the emptiness cachess' do expect(repository).to receive(:expire_emptiness_caches) @@ -637,6 +670,19 @@ describe Repository, models: true do repository.after_create end + + it 'flushes the root ref cache' do + expect(repository).to receive(:expire_root_ref_cache) + + repository.after_create + end + + it 'flushes the emptiness caches' do + expect(repository).to receive(:expire_emptiness_caches) + + repository.after_create + end + end describe "#main_language" do @@ -725,6 +771,12 @@ describe Repository, models: true do end describe '#avatar' do + it 'returns nil if repo does not exist' do + expect(repository).to receive(:exists?).and_return(false) + + expect(repository.avatar).to eq(nil) + end + it 'returns the first avatar file found in the repository' do expect(repository).to receive(:blob_at_branch). with('master', 'logo.png'). @@ -840,4 +892,21 @@ describe Repository, models: true do repository.build_cache end end + + describe '#local_branches' do + it 'returns the local branches' do + masterrev = repository.find_branch('master').target + create_remote_branch('joe', 'remote_branch', masterrev) + repository.add_branch(user, 'local_branch', masterrev) + + expect(repository.local_branches.any? { |branch| branch.name == 'remote_branch' }).to eq(false) + expect(repository.local_branches.any? { |branch| branch.name == 'local_branch' }).to eq(true) + end + end + + def create_remote_branch(remote_name, branch_name, target) + rugged = repository.rugged + rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", target) + end + end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 0ab7fd88ce6..8b2fb77e28e 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -173,6 +173,13 @@ describe User, models: true do expect(user).to be_invalid end end + + context 'owns_notification_email' do + it 'accepts temp_oauth_email emails' do + user = build(:user, email: "temp-email-for-oauth@example.com") + expect(user).to be_valid + end + end end end diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index ce55cb7b0ae..822d3ad3017 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -318,6 +318,17 @@ describe API::API, api: true do 'is too long (maximum is 255 characters)' ]) end + + context 'when an admin or owner makes the request' do + it "accepts the creation date to be set" do + post api("/projects/#{project.id}/issues", user), + title: 'new issue', labels: 'label, label2', created_at: 2.weeks.ago + + expect(response.status).to eq(201) + # this take about a second, so probably not equal + expect(Time.parse(json_response['created_at'])).to be <= 2.weeks.ago + end + end end describe 'POST /projects/:id/issues with spam filtering' do diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb index 667f0dbea5c..6943ff9d26c 100644 --- a/spec/requests/api/labels_spec.rb +++ b/spec/requests/api/labels_spec.rb @@ -23,13 +23,25 @@ describe API::API, api: true do end describe 'POST /projects/:id/labels' do - it 'should return created label' do + it 'should return created label when all params' do + post api("/projects/#{project.id}/labels", user), + name: 'Foo', + color: '#FFAABB', + description: 'test' + expect(response.status).to eq(201) + expect(json_response['name']).to eq('Foo') + expect(json_response['color']).to eq('#FFAABB') + expect(json_response['description']).to eq('test') + end + + it 'should return created label when only required params' do post api("/projects/#{project.id}/labels", user), name: 'Foo', color: '#FFAABB' expect(response.status).to eq(201) expect(json_response['name']).to eq('Foo') expect(json_response['color']).to eq('#FFAABB') + expect(json_response['description']).to be_nil end it 'should return a 400 bad request if name not given' do @@ -94,14 +106,16 @@ describe API::API, api: true do end describe 'PUT /projects/:id/labels' do - it 'should return 200 if name and colors are changed' do + it 'should return 200 if name and colors and description are changed' do put api("/projects/#{project.id}/labels", user), name: 'label1', new_name: 'New Label', - color: '#FFFFFF' + color: '#FFFFFF', + description: 'test' expect(response.status).to eq(200) expect(json_response['name']).to eq('New Label') expect(json_response['color']).to eq('#FFFFFF') + expect(json_response['description']).to eq('test') end it 'should return 200 if name is changed' do @@ -122,6 +136,15 @@ describe API::API, api: true do expect(json_response['color']).to eq('#FFFFFF') end + it 'should return 200 if description is changed' do + put api("/projects/#{project.id}/labels", user), + name: 'label1', + description: 'test' + expect(response.status).to eq(200) + expect(json_response['name']).to eq(label1.name) + expect(json_response['description']).to eq('test') + end + it 'should return 404 if label does not exist' do put api("/projects/#{project.id}/labels", user), name: 'label2', diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index c9175a4d6eb..25fa30b2f21 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -118,6 +118,7 @@ describe API::API, api: true do expect(response.status).to eq(200) expect(json_response['title']).to eq(merge_request.title) expect(json_response['iid']).to eq(merge_request.iid) + expect(json_response['work_in_progress']).to eq(false) expect(json_response['merge_status']).to eq('can_be_merged') end @@ -133,6 +134,16 @@ describe API::API, api: true do get api("/projects/#{project.id}/merge_requests/999", user) expect(response.status).to eq(404) end + + context 'Work in Progress' do + let!(:merge_request_wip) { create(:merge_request, author: user, assignee: user, source_project: project, target_project: project, title: "WIP: Test", created_at: base_time + 1.second) } + + it "should return merge_request" do + get api("/projects/#{project.id}/merge_requests/#{merge_request_wip.id}", user) + expect(response.status).to eq(200) + expect(json_response['work_in_progress']).to eq(true) + end + end end describe 'GET /projects/:id/merge_requests/:merge_request_id/commits' do diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb index db0f6e3c0f5..344f0fe0b7f 100644 --- a/spec/requests/api/milestones_spec.rb +++ b/spec/requests/api/milestones_spec.rb @@ -4,6 +4,7 @@ describe API::API, api: true do include ApiHelpers let(:user) { create(:user) } let!(:project) { create(:project, namespace: user.namespace ) } + let!(:closed_milestone) { create(:closed_milestone, project: project) } let!(:milestone) { create(:milestone, project: project) } before { project.team << [user, :developer] } @@ -20,6 +21,24 @@ describe API::API, api: true do get api("/projects/#{project.id}/milestones") expect(response.status).to eq(401) end + + it 'returns an array of active milestones' do + get api("/projects/#{project.id}/milestones?state=active", user) + + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(milestone.id) + end + + it 'returns an array of closed milestones' do + get api("/projects/#{project.id}/milestones?state=closed", user) + + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(closed_milestone.id) + end end describe 'GET /projects/:id/milestones/:milestone_id' do @@ -31,10 +50,12 @@ describe API::API, api: true do end it 'should return a project milestone by iid' do - get api("/projects/#{project.id}/milestones?iid=#{milestone.iid}", user) + get api("/projects/#{project.id}/milestones?iid=#{closed_milestone.iid}", user) + expect(response.status).to eq 200 - expect(json_response.first['title']).to eq milestone.title - expect(json_response.first['id']).to eq milestone.id + expect(json_response.size).to eq(1) + expect(json_response.first['title']).to eq closed_milestone.title + expect(json_response.first['id']).to eq closed_milestone.id end it 'should return 401 error if user not authenticated' do diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index 39f9a06fe1b..a467bc935af 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -241,4 +241,65 @@ describe API::API, api: true do end end + describe 'DELETE /projects/:id/noteable/:noteable_id/notes/:note_id' do + context 'when noteable is an Issue' do + it 'deletes a note' do + delete api("/projects/#{project.id}/issues/#{issue.id}/"\ + "notes/#{issue_note.id}", user) + + expect(response.status).to eq(200) + # Check if note is really deleted + delete api("/projects/#{project.id}/issues/#{issue.id}/"\ + "notes/#{issue_note.id}", user) + expect(response.status).to eq(404) + end + + it 'returns a 404 error when note id not found' do + delete api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user) + + expect(response.status).to eq(404) + end + end + + context 'when noteable is a Snippet' do + it 'deletes a note' do + delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\ + "notes/#{snippet_note.id}", user) + + expect(response.status).to eq(200) + # Check if note is really deleted + delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\ + "notes/#{snippet_note.id}", user) + expect(response.status).to eq(404) + end + + it 'returns a 404 error when note id not found' do + delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\ + "notes/123", user) + + expect(response.status).to eq(404) + end + end + + context 'when noteable is a Merge Request' do + it 'deletes a note' do + delete api("/projects/#{project.id}/merge_requests/"\ + "#{merge_request.id}/notes/#{merge_request_note.id}", user) + + expect(response.status).to eq(200) + # Check if note is really deleted + delete api("/projects/#{project.id}/merge_requests/"\ + "#{merge_request.id}/notes/#{merge_request_note.id}", user) + expect(response.status).to eq(404) + end + + it 'returns a 404 error when note id not found' do + delete api("/projects/#{project.id}/merge_requests/"\ + "#{merge_request.id}/notes/123", user) + + expect(response.status).to eq(404) + end + end + end + end diff --git a/spec/requests/api/project_members_spec.rb b/spec/requests/api/project_members_spec.rb index 4301588b16a..c112ca5e3ca 100644 --- a/spec/requests/api/project_members_spec.rb +++ b/spec/requests/api/project_members_spec.rb @@ -118,8 +118,10 @@ describe API::API, api: true do end describe "DELETE /projects/:id/members/:user_id" do - before { project_member } - before { project_member2 } + before do + project_member + project_member2 + end it "should remove user from project team" do expect do @@ -132,6 +134,7 @@ describe API::API, api: true do expect do delete api("/projects/#{project.id}/members/#{user3.id}", user) end.to_not change { ProjectMember.count } + expect(response.status).to eq(200) end it "should return 200 if team member already removed" do @@ -145,8 +148,19 @@ describe API::API, api: true do delete api("/projects/#{project.id}/members/1000000", user) end.to change { ProjectMember.count }.by(0) expect(response.status).to eq(200) - expect(json_response['message']).to eq("Access revoked") expect(json_response['id']).to eq(1000000) + expect(json_response['message']).to eq('Access revoked') + end + + context 'when the user is not an admin or owner' do + it 'can leave the project' do + expect do + delete api("/projects/#{project.id}/members/#{user3.id}", user3) + end.to change { ProjectMember.count }.by(-1) + + expect(response.status).to eq(200) + expect(json_response['id']).to eq(project_member2.id) + end end end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index a5d4985dc78..be2034e0f39 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -948,6 +948,78 @@ describe API::API, api: true do end end + describe 'POST /projects/:id/archive' do + context 'on an unarchived project' do + it 'archives the project' do + post api("/projects/#{project.id}/archive", user) + + expect(response.status).to eq(201) + expect(json_response['archived']).to be_truthy + end + end + + context 'on an archived project' do + before do + project.archive! + end + + it 'remains archived' do + post api("/projects/#{project.id}/archive", user) + + expect(response.status).to eq(201) + expect(json_response['archived']).to be_truthy + end + end + + context 'user without archiving rights to the project' do + before do + project.team << [user3, :developer] + end + + it 'rejects the action' do + post api("/projects/#{project.id}/archive", user3) + + expect(response.status).to eq(403) + end + end + end + + describe 'POST /projects/:id/unarchive' do + context 'on an unarchived project' do + it 'remains unarchived' do + post api("/projects/#{project.id}/unarchive", user) + + expect(response.status).to eq(201) + expect(json_response['archived']).to be_falsey + end + end + + context 'on an archived project' do + before do + project.archive! + end + + it 'unarchives the project' do + post api("/projects/#{project.id}/unarchive", user) + + expect(response.status).to eq(201) + expect(json_response['archived']).to be_falsey + end + end + + context 'user without archiving rights to the project' do + before do + project.team << [user3, :developer] + end + + it 'rejects the action' do + post api("/projects/#{project.id}/unarchive", user3) + + expect(response.status).to eq(403) + end + end + end + describe 'DELETE /projects/:id' do context 'when authenticated as user' do it 'should remove project' do diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index a15be07ed57..9f9c3b1cf4c 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -40,6 +40,23 @@ describe API::API, api: true do end end + describe 'GET /projects/:id/repository/tags/:tag_name' do + let(:tag_name) { project.repository.tag_names.sort.reverse.first } + + it 'returns a specific tag' do + get api("/projects/#{project.id}/repository/tags/#{tag_name}", user) + + expect(response.status).to eq(200) + expect(json_response['name']).to eq(tag_name) + end + + it 'returns 404 for an invalid tag name' do + get api("/projects/#{project.id}/repository/tags/foobar", user) + + expect(response.status).to eq(404) + end + end + describe 'POST /projects/:id/repository/tags' do context 'lightweight tags' do it 'should create a new tag' do diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index 8490a729e51..b40a5c1c818 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -159,18 +159,44 @@ describe GitPushService, services: true do end describe "Updates main language" do - context "before push" do it { expect(project.main_language).to eq(nil) } end context "after push" do - before do - @service = execute_service(project, user, @oldrev, @newrev, @ref) + def execute + execute_service(project, user, @oldrev, @newrev, ref) end - it { expect(@service.update_main_language).to eq(true) } - it { expect(project.main_language).to eq("Ruby") } + context "to master" do + let(:ref) { @ref } + + context 'when main_language is nil' do + it 'obtains the language from the repository' do + expect(project.repository).to receive(:main_language) + execute + end + + it 'sets the project main language' do + execute + expect(project.main_language).to eq("Ruby") + end + end + + context 'when main_language is already set' do + it 'does not check the repository' do + execute # do an initial run to simulate lang being preset + expect(project.repository).not_to receive(:main_language) + execute + end + end + end + + context "to other branch" do + let(:ref) { 'refs/heads/feature/branch' } + + it { expect(project.main_language).to eq(nil) } + end end end diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb index ade3b7850f1..2a5e4ac3ec4 100644 --- a/spec/services/issues/move_service_spec.rb +++ b/spec/services/issues/move_service_spec.rb @@ -85,6 +85,10 @@ describe Issues::MoveService, services: true do expect(old_issue.moved?).to eq true expect(old_issue.moved_to).to eq new_issue end + + it 'preserves create time' do + expect(old_issue.created_at).to eq new_issue.created_at + end end context 'issue with notes' do @@ -121,10 +125,23 @@ describe Issues::MoveService, services: true do it 'preserves orignal author of comment' do expect(user_notes.pluck(:author_id)).to all(eq(author.id)) end + end + + context 'note that has been updated' do + let!(:note) do + create(:note, noteable: old_issue, project: old_project, + author: author, updated_at: Date.yesterday, + created_at: Date.yesterday) + end + + include_context 'issue move executed' it 'preserves time when note has been created at' do - expect(old_issue.notes.first.created_at) - .to eq new_issue.notes.first.created_at + expect(new_issue.notes.first.created_at).to eq note.created_at + end + + it 'preserves time when note has been updated at' do + expect(new_issue.notes.first.updated_at).to eq note.updated_at end end @@ -143,6 +160,20 @@ describe Issues::MoveService, services: true do .to eq "Note with reference to merge request #{old_project.to_reference}!1" end end + + context 'issue description with uploads' do + let(:uploader) { build(:file_uploader, project: old_project) } + let(:description) { "Text and #{uploader.to_markdown}" } + + include_context 'issue move executed' + + it 'rewrites uploads in description' do + expect(new_issue.description).to_not eq description + expect(new_issue.description) + .to match(/Text and #{FileUploader::MARKDOWN_PATTERN}/) + expect(new_issue.description).to_not include uploader.secret + end + end end describe 'rewritting references' do diff --git a/spec/services/notes/delete_service_spec.rb b/spec/services/notes/delete_service_spec.rb new file mode 100644 index 00000000000..1d0a747a480 --- /dev/null +++ b/spec/services/notes/delete_service_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe Notes::DeleteService, services: true do + describe '#execute' do + it 'deletes a note' do + project = create(:empty_project) + issue = create(:issue, project: project) + note = create(:note, project: project, noteable: issue) + + described_class.new(project, note.author).execute(note) + + expect(project.issues.find(issue.id).notes).not_to include(note) + end + end +end diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb index 04f474c736c..32bf3acf483 100644 --- a/spec/services/projects/import_service_spec.rb +++ b/spec/services/projects/import_service_spec.rb @@ -72,6 +72,23 @@ describe Projects::ImportService, services: true do expect(result[:status]).to eq :success end + it 'flushes various caches' do + expect_any_instance_of(Gitlab::Shell).to receive(:import_repository). + with(project.path_with_namespace, project.import_url). + and_return(true) + + expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute). + and_return(true) + + expect_any_instance_of(Repository).to receive(:expire_emptiness_caches). + and_call_original + + expect_any_instance_of(Repository).to receive(:expire_exists_cache). + and_call_original + + subject.execute + end + it 'fails if importer fails' do expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.path_with_namespace, project.import_url).and_return(true) expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_return(false) diff --git a/spec/services/projects/unlink_fork_service_spec.rb b/spec/services/projects/unlink_fork_service_spec.rb new file mode 100644 index 00000000000..23f5555d3e0 --- /dev/null +++ b/spec/services/projects/unlink_fork_service_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +describe Projects::UnlinkForkService, services: true do + subject { Projects::UnlinkForkService.new(fork_project, user) } + + let(:fork_link) { create(:forked_project_link) } + let(:fork_project) { fork_link.forked_to_project } + let(:user) { create(:user) } + + context 'with opened merge request on the source project' do + let(:merge_request) { create(:merge_request, source_project: fork_project, target_project: fork_link.forked_from_project) } + let(:mr_close_service) { MergeRequests::CloseService.new(fork_project, user) } + + before do + allow(MergeRequests::CloseService).to receive(:new). + with(fork_project, user). + and_return(mr_close_service) + end + + it 'close all pending merge requests' do + expect(mr_close_service).to receive(:execute).with(merge_request) + + subject.execute + end + end + + it 'remove fork relation' do + expect(fork_project.forked_project_link).to receive(:destroy) + + subject.execute + end +end diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index b4728807b8b..82b7fbfa816 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -2,22 +2,25 @@ require 'spec_helper' describe TodoService, services: true do let(:author) { create(:user) } - let(:john_doe) { create(:user, username: 'john_doe') } - let(:michael) { create(:user, username: 'michael') } - let(:stranger) { create(:user, username: 'stranger') } + let(:assignee) { create(:user) } + let(:non_member) { create(:user) } + let(:member) { create(:user) } + let(:admin) { create(:admin) } + let(:john_doe) { create(:user) } let(:project) { create(:project) } - let(:mentions) { [author.to_reference, john_doe.to_reference, michael.to_reference, stranger.to_reference].join(' ') } + let(:mentions) { [author, assignee, john_doe, member, non_member, admin].map(&:to_reference).join(' ') } let(:service) { described_class.new } before do project.team << [author, :developer] + project.team << [member, :developer] project.team << [john_doe, :developer] - project.team << [michael, :developer] end describe 'Issues' do let(:issue) { create(:issue, project: project, assignee: john_doe, author: author, description: mentions) } let(:unassigned_issue) { create(:issue, project: project, assignee: nil) } + let(:confidential_issue) { create(:issue, :confidential, project: project, author: author, assignee: assignee, description: mentions) } describe '#new_issue' do it 'creates a todo if assigned' do @@ -37,10 +40,20 @@ describe TodoService, services: true do it 'creates a todo for each valid mentioned user' do service.new_issue(issue, author) - should_create_todo(user: michael, target: issue, action: Todo::MENTIONED) + should_create_todo(user: member, target: issue, action: Todo::MENTIONED) should_not_create_todo(user: author, target: issue, action: Todo::MENTIONED) should_not_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED) - should_not_create_todo(user: stranger, target: issue, action: Todo::MENTIONED) + should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED) + end + + it 'does not create todo for non project members when issue is confidential' do + service.new_issue(confidential_issue, john_doe) + + should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::ASSIGNED) + should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) + should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) + should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) + should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) end end @@ -48,16 +61,26 @@ describe TodoService, services: true do it 'creates a todo for each valid mentioned user' do service.update_issue(issue, author) - should_create_todo(user: michael, target: issue, action: Todo::MENTIONED) + should_create_todo(user: member, target: issue, action: Todo::MENTIONED) should_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED) should_not_create_todo(user: author, target: issue, action: Todo::MENTIONED) - should_not_create_todo(user: stranger, target: issue, action: Todo::MENTIONED) + should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED) end it 'does not create a todo if user was already mentioned' do - create(:todo, :mentioned, user: michael, project: project, target: issue, author: author) + create(:todo, :mentioned, user: member, project: project, target: issue, author: author) - expect { service.update_issue(issue, author) }.not_to change(michael.todos, :count) + expect { service.update_issue(issue, author) }.not_to change(member.todos, :count) + end + + it 'does not create todo for non project members when issue is confidential' do + service.update_issue(confidential_issue, john_doe) + + should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) + should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) + should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) + should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) + should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) end end @@ -109,8 +132,10 @@ describe TodoService, services: true do describe '#new_note' do let!(:first_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) } let!(:second_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) } + let(:confidential_issue) { create(:issue, :confidential, project: project, author: author, assignee: assignee) } let(:note) { create(:note, project: project, noteable: issue, author: john_doe, note: mentions) } let(:note_on_commit) { create(:note_on_commit, project: project, author: john_doe, note: mentions) } + let(:note_on_confidential_issue) { create(:note_on_issue, noteable: confidential_issue, project: project, note: mentions) } let(:note_on_project_snippet) { create(:note_on_project_snippet, project: project, author: john_doe, note: mentions) } let(:award_note) { create(:note, :award, project: project, noteable: issue, author: john_doe, note: 'thumbsup') } let(:system_note) { create(:system_note, project: project, noteable: issue) } @@ -142,19 +167,29 @@ describe TodoService, services: true do it 'creates a todo for each valid mentioned user' do service.new_note(note, john_doe) - should_create_todo(user: michael, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) + should_create_todo(user: member, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) should_create_todo(user: author, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) should_not_create_todo(user: john_doe, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) - should_not_create_todo(user: stranger, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) + should_not_create_todo(user: non_member, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) + end + + it 'does not create todo for non project members when leaving a note on a confidential issue' do + service.new_note(note_on_confidential_issue, john_doe) + + should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue) + should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue) + should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue) + should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue) + should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue) end it 'creates a todo for each valid mentioned user when leaving a note on commit' do service.new_note(note_on_commit, john_doe) - should_create_todo(user: michael, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) + should_create_todo(user: member, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) should_create_todo(user: author, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) should_not_create_todo(user: john_doe, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) - should_not_create_todo(user: stranger, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) + should_not_create_todo(user: non_member, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) end it 'does not create todo when leaving a note on snippet' do @@ -185,10 +220,10 @@ describe TodoService, services: true do it 'creates a todo for each valid mentioned user' do service.new_merge_request(mr_assigned, author) - should_create_todo(user: michael, target: mr_assigned, action: Todo::MENTIONED) + should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED) - should_not_create_todo(user: stranger, target: mr_assigned, action: Todo::MENTIONED) + should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED) end end @@ -196,16 +231,16 @@ describe TodoService, services: true do it 'creates a todo for each valid mentioned user' do service.update_merge_request(mr_assigned, author) - should_create_todo(user: michael, target: mr_assigned, action: Todo::MENTIONED) + should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED) should_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED) - should_not_create_todo(user: stranger, target: mr_assigned, action: Todo::MENTIONED) + should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED) end it 'does not create a todo if user was already mentioned' do - create(:todo, :mentioned, user: michael, project: project, target: mr_assigned, author: author) + create(:todo, :mentioned, user: member, project: project, target: mr_assigned, author: author) - expect { service.update_merge_request(mr_assigned, author) }.not_to change(michael.todos, :count) + expect { service.update_merge_request(mr_assigned, author) }.not_to change(member.todos, :count) end end diff --git a/spec/support/carrierwave.rb b/spec/support/carrierwave.rb new file mode 100644 index 00000000000..aa89afd8fb3 --- /dev/null +++ b/spec/support/carrierwave.rb @@ -0,0 +1,7 @@ +CarrierWave.root = 'tmp/tests/uploads' + +RSpec.configure do |config| + config.after(:suite) do + FileUtils.rm_rf('tmp/tests/uploads') + end +end diff --git a/spec/support/filter_spec_helper.rb b/spec/support/filter_spec_helper.rb index ef5ea7d626e..e849a9633b9 100644 --- a/spec/support/filter_spec_helper.rb +++ b/spec/support/filter_spec_helper.rb @@ -78,6 +78,6 @@ module FilterSpecHelper # Shortcut to Rails' auto-generated routes helpers, to avoid including the # module def urls - Gitlab::Application.routes.url_helpers + Gitlab::Routing.url_helpers end end diff --git a/spec/support/markdown_feature.rb b/spec/support/markdown_feature.rb index 73c6792b65f..b87cd6bbca2 100644 --- a/spec/support/markdown_feature.rb +++ b/spec/support/markdown_feature.rb @@ -106,7 +106,7 @@ class MarkdownFeature end def urls - Gitlab::Application.routes.url_helpers + Gitlab::Routing.url_helpers end def raw_markdown diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb index 1d52489e804..43cb6ef43f2 100644 --- a/spec/support/matchers/markdown_matchers.rb +++ b/spec/support/matchers/markdown_matchers.rb @@ -13,7 +13,7 @@ module MarkdownMatchers set_default_markdown_messages match do |actual| - link = actual.at_css('a:contains("Relative Link")') + link = actual.at_css('a:contains("Relative Link")') image = actual.at_css('img[alt="Relative Image"]') expect(link['href']).to end_with('master/doc/README.md') @@ -72,14 +72,15 @@ module MarkdownMatchers have_css("img[src$='#{src}']") end + prefix = '/namespace1/gitlabhq/wikis' set_default_markdown_messages match do |actual| - expect(actual).to have_link('linked-resource', href: 'linked-resource') - expect(actual).to have_link('link-text', href: 'linked-resource') + expect(actual).to have_link('linked-resource', href: "#{prefix}/linked-resource") + expect(actual).to have_link('link-text', href: "#{prefix}/linked-resource") expect(actual).to have_link('http://example.com', href: 'http://example.com') expect(actual).to have_link('link-text', href: 'http://example.com/pdfs/gollum.pdf') - expect(actual).to have_image('/gitlabhq/wikis/images/example.jpg') + expect(actual).to have_image("#{prefix}/images/example.jpg") expect(actual).to have_image('http://example.com/images/example.jpg') end end diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index 0d1bd030f3c..71664bb192e 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -15,6 +15,7 @@ module TestEnv 'lfs' => 'be93687', 'master' => '5937ac0', "'test'" => 'e56497b', + 'orphaned-branch' => '45127a9', } # gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index 320be9a0b61..05fc4c4554f 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -7,8 +7,12 @@ describe 'gitlab:app namespace rake task' do Rake.application.rake_require 'tasks/gitlab/backup' Rake.application.rake_require 'tasks/gitlab/shell' Rake.application.rake_require 'tasks/gitlab/db' + # empty task as env is already loaded Rake::Task.define_task :environment + + # We need this directory to run `gitlab:backup:create` task + FileUtils.mkdir_p('public/uploads') end def run_rake_task(task_name) diff --git a/spec/workers/merge_worker_spec.rb b/spec/workers/merge_worker_spec.rb index b11c5de94e3..1abd87d7d33 100644 --- a/spec/workers/merge_worker_spec.rb +++ b/spec/workers/merge_worker_spec.rb @@ -22,6 +22,8 @@ describe MergeWorker do merge_request.reload expect(merge_request).to be_merged + + source_project.repository.expire_branches_cache expect(source_project.repository.branch_names).not_to include('markdown') end end diff --git a/spec/workers/project_cache_worker_spec.rb b/spec/workers/project_cache_worker_spec.rb new file mode 100644 index 00000000000..7e59bd2fced --- /dev/null +++ b/spec/workers/project_cache_worker_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe ProjectCacheWorker do + let(:project) { create(:project) } + + subject { described_class.new } + + describe '#perform' do + it 'updates project cache data' do + + expect_any_instance_of(Repository).to receive(:size) + expect_any_instance_of(Repository).to receive(:commit_count) + + expect_any_instance_of(Project).to receive(:update_repository_size) + expect_any_instance_of(Project).to receive(:update_commit_count) + + subject.perform(project.id) + end + + it 'handles missing repository data' do + expect_any_instance_of(Repository).to receive(:exists?).and_return(false) + expect_any_instance_of(Repository).not_to receive(:size) + + subject.perform(project.id) + end + end +end |