diff options
Diffstat (limited to 'spec')
99 files changed, 2358 insertions, 487 deletions
diff --git a/spec/controllers/projects/badges_controller_spec.rb b/spec/controllers/projects/badges_controller_spec.rb new file mode 100644 index 00000000000..d68200164e4 --- /dev/null +++ b/spec/controllers/projects/badges_controller_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe Projects::BadgesController do + let(:project) { pipeline.project } + let!(:pipeline) { create(:ci_empty_pipeline) } + let(:user) { create(:user) } + + before do + project.add_master(user) + sign_in(user) + end + + it 'requests the pipeline badge successfully' do + get_badge(:pipeline) + + expect(response).to have_http_status(:ok) + end + + it 'requests the coverage badge successfully' do + get_badge(:coverage) + + expect(response).to have_http_status(:ok) + end + + def get_badge(badge) + get badge, namespace_id: project.namespace.to_param, project_id: project, ref: pipeline.ref, format: :svg + end +end diff --git a/spec/controllers/projects/hooks_controller_spec.rb b/spec/controllers/projects/hooks_controller_spec.rb new file mode 100644 index 00000000000..b93ab220f4d --- /dev/null +++ b/spec/controllers/projects/hooks_controller_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe Projects::HooksController do + let(:project) { create(:empty_project) } + let(:user) { create(:user) } + + before do + project.team << [user, :master] + sign_in(user) + end + + describe '#index' do + it 'redirects to settings/integrations page' do + get(:index, namespace_id: project.namespace, project_id: project) + + expect(response).to redirect_to( + project_settings_integrations_path(project) + ) + end + end +end diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 18d0be3c103..2c57f3bcf8d 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -7,16 +7,30 @@ describe Projects::IssuesController do describe "GET #index" do context 'external issue tracker' do - let!(:service) do - create(:custom_issue_tracker_service, project: project, title: 'Custom Issue Tracker', project_url: 'http://test.com') + before do + sign_in(user) + project.add_developer(user) + create(:jira_service, project: project) end - it 'redirects to the external issue tracker' do - controller.instance_variable_set(:@project, project) + context 'when GitLab issues disabled' do + it 'returns 404 status' do + project.issues_enabled = false + project.save! - get :index, namespace_id: project.namespace, project_id: project + get :index, namespace_id: project.namespace, project_id: project + + expect(response).to have_http_status(404) + end + end + + context 'when GitLab issues enabled' do + it 'renders the "index" template' do + get :index, namespace_id: project.namespace, project_id: project - expect(response).to redirect_to(service.issue_tracker_path) + expect(response).to have_http_status(200) + expect(response).to render_template(:index) + end end end @@ -42,15 +56,7 @@ describe Projects::IssuesController do it "returns 404 when issues are disabled" do project.issues_enabled = false - project.save - - get :index, namespace_id: project.namespace, project_id: project - expect(response).to have_http_status(404) - end - - it "returns 404 when external issue tracker is enabled" do - controller.instance_variable_set(:@project, project) - allow(project).to receive(:default_issues_tracker?).and_return(false) + project.save! get :index, namespace_id: project.namespace, project_id: project expect(response).to have_http_status(404) @@ -148,14 +154,29 @@ describe Projects::IssuesController do before do sign_in(user) project.team << [user, :developer] + + external = double + allow(project).to receive(:external_issue_tracker).and_return(external) end - it 'redirects to the external issue tracker' do - controller.instance_variable_set(:@project, project) + context 'when GitLab issues disabled' do + it 'returns 404 status' do + project.issues_enabled = false + project.save! - get :new, namespace_id: project.namespace, project_id: project + get :new, namespace_id: project.namespace, project_id: project - expect(response).to redirect_to('http://test.com') + expect(response).to have_http_status(404) + end + end + + context 'when GitLab issues enabled' do + it 'renders the "new" template' do + get :new, namespace_id: project.namespace, project_id: project + + expect(response).to have_http_status(200) + expect(response).to render_template(:new) + end end end end @@ -806,7 +827,7 @@ describe Projects::IssuesController do delete :destroy, namespace_id: project.namespace, project_id: project, id: issue.iid expect(response).to have_http_status(302) - expect(controller).to set_flash[:notice].to(/The issue was successfully deleted\./).now + expect(controller).to set_flash[:notice].to(/The issue was successfully deleted\./) end it 'delegates the update of the todos count cache to TodoService' do diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index c193babead0..2fce4b7a85f 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -439,7 +439,7 @@ describe Projects::MergeRequestsController do delete :destroy, namespace_id: project.namespace, project_id: project, id: merge_request.iid expect(response).to have_http_status(302) - expect(controller).to set_flash[:notice].to(/The merge request was successfully deleted\./).now + expect(controller).to set_flash[:notice].to(/The merge request was successfully deleted\./) end it 'delegates the update of the todos count cache to TodoService' do diff --git a/spec/controllers/sent_notifications_controller_spec.rb b/spec/controllers/sent_notifications_controller_spec.rb index 7340a4e16c0..c8771eda313 100644 --- a/spec/controllers/sent_notifications_controller_spec.rb +++ b/spec/controllers/sent_notifications_controller_spec.rb @@ -23,7 +23,7 @@ describe SentNotificationsController, type: :controller do end it 'sets the flash message' do - expect(controller).to set_flash[:notice].to(/unsubscribed/).now + expect(controller).to set_flash[:notice].to(/unsubscribed/) end it 'redirects to the login page' do @@ -83,7 +83,7 @@ describe SentNotificationsController, type: :controller do end it 'sets the flash message' do - expect(controller).to set_flash[:notice].to(/unsubscribed/).now + expect(controller).to set_flash[:notice].to(/unsubscribed/) end it 'redirects to the issue page' do @@ -109,7 +109,7 @@ describe SentNotificationsController, type: :controller do end it 'sets the flash message' do - expect(controller).to set_flash[:notice].to(/unsubscribed/).now + expect(controller).to set_flash[:notice].to(/unsubscribed/) end it 'redirects to the merge request page' do diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb index 2b4e8723b48..a22fd8eaf9b 100644 --- a/spec/controllers/sessions_controller_spec.rb +++ b/spec/controllers/sessions_controller_spec.rb @@ -1,9 +1,11 @@ require 'spec_helper' describe SessionsController do + include DeviseHelpers + describe '#new' do before do - @request.env['devise.mapping'] = Devise.mappings[:user] + set_devise_mapping(context: @request) end context 'when auto sign-in is enabled' do @@ -34,7 +36,7 @@ describe SessionsController do describe '#create' do before do - @request.env['devise.mapping'] = Devise.mappings[:user] + set_devise_mapping(context: @request) end context 'when using standard authentications' do @@ -257,7 +259,7 @@ describe SessionsController do describe '#new' do before do - @request.env['devise.mapping'] = Devise.mappings[:user] + set_devise_mapping(context: @request) end it 'redirects correctly for referer on same host with params' do diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index 678cebe365b..5bba1dec7db 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -110,7 +110,7 @@ FactoryGirl.define do end after(:build) do |build, evaluator| - build.project = build.pipeline.project + build.project ||= build.pipeline.project end factory :ci_not_started_build do diff --git a/spec/factories/project_hooks.rb b/spec/factories/project_hooks.rb index cd754ea235f..d754e980931 100644 --- a/spec/factories/project_hooks.rb +++ b/spec/factories/project_hooks.rb @@ -2,6 +2,7 @@ FactoryGirl.define do factory :project_hook do url { generate(:url) } enable_ssl_verification false + project factory: :empty_project trait :token do token { SecureRandom.hex(10) } diff --git a/spec/features/admin/admin_appearance_spec.rb b/spec/features/admin/admin_appearance_spec.rb index b9e361328df..2f90f668e89 100644 --- a/spec/features/admin/admin_appearance_spec.rb +++ b/spec/features/admin/admin_appearance_spec.rb @@ -63,11 +63,11 @@ feature 'Admin Appearance', feature: true do end def logo_selector - '//img[@src^="/uploads/-/system/appearance/logo"]' + '//img[data-src^="/uploads/-/system/appearance/logo"]' end def header_logo_selector - '//img[@src^="/uploads/-/system/appearance/header_logo"]' + '//img[data-src^="/uploads/-/system/appearance/header_logo"]' end def logo_fixture diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb index 1e675fc0ce7..9a438b65e68 100644 --- a/spec/features/admin/admin_hooks_spec.rb +++ b/spec/features/admin/admin_hooks_spec.rb @@ -74,11 +74,13 @@ describe 'Admin::Hooks', feature: true do end end - describe 'Test' do + describe 'Test', js: true do before do WebMock.stub_request(:post, @system_hook.url) visit admin_hooks_path - click_link 'Test hook' + + find('.hook-test-button.dropdown').click + click_link 'Push events' end it { expect(current_path).to eq(admin_hooks_path) } diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb index b06e7e5037c..46bab3763cc 100644 --- a/spec/features/admin/admin_runners_spec.rb +++ b/spec/features/admin/admin_runners_spec.rb @@ -19,7 +19,7 @@ describe "Admin Runners" do end it 'has all necessary texts' do - expect(page).to have_text "To register a new Runner" + expect(page).to have_text "How to setup" expect(page).to have_text "Runners with last contact more than a minute ago: 1" end @@ -54,7 +54,7 @@ describe "Admin Runners" do end it 'has all necessary texts including no runner message' do - expect(page).to have_text "To register a new Runner" + expect(page).to have_text "How to setup" expect(page).to have_text "Runners with last contact more than a minute ago: 0" expect(page).to have_text 'No runners found' end @@ -163,12 +163,11 @@ describe "Admin Runners" do end it 'has a registration token' do - expect(page).to have_content("Registration token is #{token}") - expect(page).to have_selector('#runners-token', text: token) + expect(page.find('#registration_token')).to have_content(token) end describe 'reload registration token' do - let(:page_token) { find('#runners-token').text } + let(:page_token) { find('#registration_token').text } before do click_button 'Reset runners registration token' diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb index 69c1a2ed89a..2a5ef08da60 100644 --- a/spec/features/dashboard/issues_spec.rb +++ b/spec/features/dashboard/issues_spec.rb @@ -78,5 +78,23 @@ RSpec.describe 'Dashboard Issues', feature: true do expect(page).not_to have_content(project_with_issues_disabled.name_with_namespace) end end + + it 'shows the new issue page', js: true do + Gitlab::Application.routes.default_url_options = { + host: Capybara.current_session.server.host, + port: Capybara.current_session.server.port, + protocol: 'http' + } + + find('.new-project-item-select-button').trigger('click') + wait_for_requests + find('.select2-results li').click + + expect(page).to have_current_path("/#{project.path_with_namespace}/issues/new") + + page.within('#content-body') do + expect(page).to have_selector('.issue-form') + end + end end end diff --git a/spec/features/explore/new_menu_spec.rb b/spec/features/explore/new_menu_spec.rb index 7dd69f550ac..e51d527bdf9 100644 --- a/spec/features/explore/new_menu_spec.rb +++ b/spec/features/explore/new_menu_spec.rb @@ -1,17 +1,13 @@ require 'spec_helper' feature 'Top Plus Menu', feature: true, js: true do - let(:user) { create :user } - let(:guest_user) { create :user} + let(:user) { create(:user) } let(:group) { create(:group) } let(:project) { create(:project, :repository, creator: user, namespace: user.namespace) } let(:public_project) { create(:project, :public) } before do group.add_owner(user) - group.add_guest(guest_user) - - project.add_guest(guest_user) end context 'used by full user' do @@ -39,7 +35,7 @@ feature 'Top Plus Menu', feature: true, js: true do scenario 'click on New snippet shows new snippet page' do visit root_dashboard_path - + click_topmenuitem("New snippet") expect(page).to have_content('New Snippet') @@ -102,7 +98,12 @@ feature 'Top Plus Menu', feature: true, js: true do end context 'used by guest user' do + let(:guest_user) { create(:user) } + before do + group.add_guest(guest_user) + project.add_guest(guest_user) + sign_in(guest_user) end @@ -153,7 +154,7 @@ feature 'Top Plus Menu', feature: true, js: true do scenario 'has no New project for group menu item' do visit group_path(group) - + expect(find('.header-new.dropdown')).not_to have_selector('.header-new-group-project') end end @@ -168,5 +169,5 @@ feature 'Top Plus Menu', feature: true, js: true do def hasnot_topmenuitem(item_name) expect(find('.header-new.dropdown')).not_to have_content(item_name) - end + end end diff --git a/spec/features/issuables/markdown_references_spec.rb b/spec/features/issuables/markdown_references_spec.rb new file mode 100644 index 00000000000..f51b2e4001a --- /dev/null +++ b/spec/features/issuables/markdown_references_spec.rb @@ -0,0 +1,193 @@ +require 'rails_helper' + +describe 'Markdown References', :feature, :js do + let(:user) { create(:user) } + let(:actual_project) { create(:project, :public) } + let(:merge_request) { create(:merge_request, target_project: actual_project, source_project: actual_project)} + let(:issue_actual_project) { create(:issue, project: actual_project) } + let!(:other_project) { create(:empty_project, :public) } + let!(:issue_other_project) { create(:issue, project: other_project) } + let(:issues) { [issue_actual_project, issue_other_project] } + + def build_note + markdown = "Referencing internal issue #{issue_actual_project.to_reference}, " + + "cross-project #{issue_other_project.to_reference(actual_project)} external JIRA-5 " + + "and non existing #999" + + page.within('#diff-notes-app') do + fill_in 'note_note', with: markdown + end + end + + shared_examples 'correct references' do + before do + remotelink = double(:remotelink, all: [], build: double(save!: true)) + + stub_request(:get, "https://jira.example.com/rest/api/2/issue/JIRA-5") + stub_request(:post, "https://jira.example.com/rest/api/2/issue/JIRA-5/comment") + allow_any_instance_of(JIRA::Resource::Issue).to receive(:remotelink).and_return(remotelink) + + sign_in(user) + visit merge_request_path(merge_request) + build_note + end + + def links_expectations + issues.each do |issue| + if referenced_issues.include?(issue) + expect(page).to have_link(issue.to_reference, href: issue_path(issue)) + else + expect(page).not_to have_link(issue.to_reference, href: issue_path(issue)) + end + end + + if jira_referenced + expect(page).to have_link('JIRA-5', href: 'https://jira.example.com/browse/JIRA-5') + else + expect(page).not_to have_link('JIRA-5', href: 'https://jira.example.com/browse/JIRA-5') + end + + expect(page).not_to have_link('#999') + end + + it 'creates a link to the referenced issue on the preview' do + find('.js-md-preview-button').click + wait_for_requests + + page.within('.md-preview-holder') do + links_expectations + end + end + + it 'creates a link to the referenced issue after submit' do + click_button 'Comment' + wait_for_requests + + page.within('#diff-notes-app') do + links_expectations + end + end + + it 'creates a note on the referenced issues' do + click_button 'Comment' + wait_for_requests + + if referenced_issues.include?(issue_actual_project) + visit issue_path(issue_actual_project) + + page.within('#notes') do + expect(page).to have_content( + "#{user.to_reference} mentioned in merge request #{merge_request.to_reference}" + ) + end + end + + if referenced_issues.include?(issue_other_project) + visit issue_path(issue_other_project) + + page.within('#notes') do + expect(page).to have_content( + "#{user.to_reference} mentioned in merge request #{merge_request.to_reference(other_project)}" + ) + end + end + end + end + + context 'when internal issues tracker is enabled for the other project' do + context 'when only internal issues tracker is enabled for the actual project' do + include_examples 'correct references' do + let(:referenced_issues) { [issue_actual_project, issue_other_project] } + let(:jira_referenced) { false } + end + end + + context 'when both external and internal issues trackers are enabled for the actual project' do + before do + create(:jira_service, project: actual_project) + end + + include_examples 'correct references' do + let(:referenced_issues) { [issue_actual_project, issue_other_project] } + let(:jira_referenced) { true } + end + end + + context 'when only external issues tracker is enabled for the actual project' do + before do + create(:jira_service, project: actual_project) + + actual_project.issues_enabled = false + actual_project.save! + end + + include_examples 'correct references' do + let(:referenced_issues) { [issue_other_project] } + let(:jira_referenced) { true } + end + end + + context 'when no tracker is enabled for the actual project' do + before do + actual_project.issues_enabled = false + actual_project.save! + end + + include_examples 'correct references' do + let(:referenced_issues) { [issue_other_project] } + let(:jira_referenced) { false } + end + end + end + + context 'when internal issues tracker is disabled for the other project' do + before do + other_project.issues_enabled = false + other_project.save! + end + + context 'when only internal issues tracker is enabled for the actual project' do + include_examples 'correct references' do + let(:referenced_issues) { [issue_actual_project] } + let(:jira_referenced) { false } + end + end + + context 'when both external and internal issues trackers are enabled for the actual project' do + before do + create(:jira_service, project: actual_project) + end + + include_examples 'correct references' do + let(:referenced_issues) { [issue_actual_project] } + let(:jira_referenced) { true } + end + end + + context 'when only external issues tracker is enabled for the actual project' do + before do + create(:jira_service, project: actual_project) + + actual_project.issues_enabled = false + actual_project.save! + end + + include_examples 'correct references' do + let(:referenced_issues) { [] } + let(:jira_referenced) { true } + end + end + + context 'when no issues tracker is enabled for the actual project' do + before do + actual_project.issues_enabled = false + actual_project.save! + end + + include_examples 'correct references' do + let(:referenced_issues) { [] } + let(:jira_referenced) { false } + end + end + end +end diff --git a/spec/features/issues/user_uses_slash_commands_spec.rb b/spec/features/issues/user_uses_slash_commands_spec.rb index 1cd1f016674..0c3c27e3e45 100644 --- a/spec/features/issues/user_uses_slash_commands_spec.rb +++ b/spec/features/issues/user_uses_slash_commands_spec.rb @@ -21,6 +21,16 @@ feature 'Issues > User uses quick actions', feature: true, js: true do wait_for_requests end + describe 'time tracking' do + let(:issue) { create(:issue, project: project) } + + before do + visit project_issue_path(project, issue) + end + + it_behaves_like 'issuable time tracker' + end + describe 'adding a due date from note' do let(:issue) { create(:issue, project: project) } @@ -99,39 +109,50 @@ feature 'Issues > User uses quick actions', feature: true, js: true do end end - describe 'Issuable time tracking' do + describe 'toggling the WIP prefix from the title from note' do let(:issue) { create(:issue, project: project) } - before do - project.team << [user, :developer] + it 'does not recognize the command nor create a note' do + write_note("/wip") + + expect(page).not_to have_content '/wip' end + end - context 'Issue' do - before do - visit project_issue_path(project, issue) - end + describe 'mark issue as duplicate' do + let(:issue) { create(:issue, project: project) } + let(:original_issue) { create(:issue, project: project) } - it_behaves_like 'issuable time tracker' - end + context 'when the current user can update issues' do + it 'does not create a note, and marks the issue as a duplicate' do + write_note("/duplicate ##{original_issue.to_reference}") - context 'Merge Request' do - let(:merge_request) { create(:merge_request, source_project: project) } + expect(page).not_to have_content "/duplicate #{original_issue.to_reference}" + expect(page).to have_content 'Commands applied' + expect(page).to have_content "marked this issue as a duplicate of #{original_issue.to_reference}" - before do - visit project_merge_request_path(project, merge_request) + expect(issue.reload).to be_closed end - - it_behaves_like 'issuable time tracker' end - end - describe 'toggling the WIP prefix from the title from note' do - let(:issue) { create(:issue, project: project) } + context 'when the current user cannot update the issue' do + let(:guest) { create(:user) } + before do + project.team << [guest, :guest] + gitlab_sign_out + sign_in(guest) + visit project_issue_path(project, issue) + end - it 'does not recognize the command nor create a note' do - write_note("/wip") + it 'does not create a note, and does not mark the issue as a duplicate' do + write_note("/duplicate ##{original_issue.to_reference}") - expect(page).not_to have_content '/wip' + expect(page).to have_content "/duplicate ##{original_issue.to_reference}" + expect(page).not_to have_content 'Commands applied' + expect(page).not_to have_content "marked this issue as a duplicate of #{original_issue.to_reference}" + + expect(issue.reload).to be_open + end end end end diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb index 534be3ab5a7..1aca3e3a9fd 100644 --- a/spec/features/markdown_spec.rb +++ b/spec/features/markdown_spec.rb @@ -100,7 +100,7 @@ describe 'GitLab Markdown', feature: true do end it 'permits img elements' do - expect(doc).to have_selector('img[src*="smile.png"]') + expect(doc).to have_selector('img[data-src*="smile.png"]') end it 'permits br elements' do diff --git a/spec/features/merge_requests/user_uses_slash_commands_spec.rb b/spec/features/merge_requests/user_uses_slash_commands_spec.rb index 434f5a7c0ac..b2187e01bdb 100644 --- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb +++ b/spec/features/merge_requests/user_uses_slash_commands_spec.rb @@ -24,6 +24,14 @@ feature 'Merge Requests > User uses quick actions', feature: true, js: true do wait_for_requests end + describe 'time tracking' do + before do + visit project_merge_request_path(project, merge_request) + end + + it_behaves_like 'issuable time tracker' + end + describe 'toggling the WIP prefix in the title from note' do context 'when the current user can toggle the WIP prefix' do it 'adds the WIP: prefix to the title' do diff --git a/spec/features/oauth_login_spec.rb b/spec/features/oauth_login_spec.rb index 42764e808e6..0064c9ef25e 100644 --- a/spec/features/oauth_login_spec.rb +++ b/spec/features/oauth_login_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' feature 'OAuth Login', js: true do + include DeviseHelpers + def enter_code(code) fill_in 'user_otp_attempt', with: code click_button 'Verify code' @@ -8,7 +10,7 @@ feature 'OAuth Login', js: true do def stub_omniauth_config(provider) OmniAuth.config.add_mock(provider, OmniAuth::AuthHash.new(provider: provider.to_s, uid: "12345")) - Rails.application.env_config['devise.mapping'] = Devise.mappings[:user] + set_devise_mapping(context: Rails.application) Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[provider] end diff --git a/spec/features/projects/badges/list_spec.rb b/spec/features/projects/badges/list_spec.rb index 161d731f524..fd8e9232b02 100644 --- a/spec/features/projects/badges/list_spec.rb +++ b/spec/features/projects/badges/list_spec.rb @@ -10,16 +10,16 @@ feature 'list of badges' do end scenario 'user wants to see build status badge' do - page.within('.build-status') do - expect(page).to have_content 'build status' + page.within('.pipeline-status') do + expect(page).to have_content 'pipeline status' expect(page).to have_content 'Markdown' expect(page).to have_content 'HTML' expect(page).to have_content 'AsciiDoc' expect(page).to have_css('.highlight', count: 3) - expect(page).to have_xpath("//img[@alt='build status']") + expect(page).to have_xpath("//img[@alt='pipeline status']") page.within('.highlight', match: :first) do - expect(page).to have_content 'badges/master/build.svg' + expect(page).to have_content 'badges/master/pipeline.svg' end end end @@ -40,14 +40,14 @@ feature 'list of badges' do end scenario 'user changes current ref of build status badge', js: true do - page.within('.build-status') do + page.within('.pipeline-status') do first('.js-project-refs-dropdown').click page.within '.project-refs-form' do click_link 'improve/awesome' end - expect(page).to have_content 'badges/improve/awesome/build.svg' + expect(page).to have_content 'badges/improve/awesome/pipeline.svg' end end end diff --git a/spec/features/projects/badges/pipeline_badge_spec.rb b/spec/features/projects/badges/pipeline_badge_spec.rb new file mode 100644 index 00000000000..b83ea8f4eaa --- /dev/null +++ b/spec/features/projects/badges/pipeline_badge_spec.rb @@ -0,0 +1,70 @@ +require 'spec_helper' + +feature 'Pipeline Badge' do + set(:project) { create(:project, :repository, :public) } + let(:ref) { project.default_branch } + + # this can't be tested in the controller, as it bypasses the rails router + # and constructs a route based on the controller being tested + # Keep around until 10.0, see gitlab-org/gitlab-ce#35307 + context 'when the deprecated badge is requested' do + it 'displays the badge' do + visit build_project_badges_path(project, ref: ref, format: :svg) + + expect(page.status_code).to eq(200) + end + end + + context 'when the project has a pipeline' do + let!(:pipeline) { create(:ci_empty_pipeline, project: project, ref: ref, sha: project.commit(ref).sha) } + let!(:job) { create(:ci_build, pipeline: pipeline) } + + context 'when the pipeline was successfull' do + it 'displays so on the badge' do + job.success + + visit pipeline_project_badges_path(project, ref: ref, format: :svg) + + expect(page.status_code).to eq(200) + expect_badge('passed') + end + end + + context 'when the pipeline failed' do + it 'shows displays so on the badge' do + job.drop + + visit pipeline_project_badges_path(project, ref: ref, format: :svg) + + expect(page.status_code).to eq(200) + expect_badge('failed') + end + end + + context 'when the pipeline is running' do + it 'shows displays so on the badge' do + create(:ci_build, pipeline: pipeline, name: 'second build', status_event: 'run') + + visit pipeline_project_badges_path(project, ref: ref, format: :svg) + + expect(page.status_code).to eq(200) + expect_badge('running') + end + end + + context 'when a new pipeline is created' do + it 'shows a fresh badge' do + visit pipeline_project_badges_path(project, ref: ref, format: :svg) + + expect(page.status_code).to eq(200) + expect(page.response_headers['Cache-Control']).to include 'no-cache' + end + end + + def expect_badge(status) + svg = Nokogiri::XML.parse(page.body) + expect(page.response_headers['Content-Type']).to include('image/svg+xml') + expect(svg.at(%Q{text:contains("#{status}")})).to be_truthy + end + end +end diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb index 827e02a58d0..1588f8a828a 100644 --- a/spec/features/projects/features_visibility_spec.rb +++ b/spec/features/projects/features_visibility_spec.rb @@ -39,14 +39,25 @@ describe 'Edit Project Settings', feature: true do end end - context "When external issue tracker is enabled" do - it "does not hide issues tab" do - project.project_feature.update(issues_access_level: ProjectFeature::DISABLED) + context 'When external issue tracker is enabled and issues enabled on project settings' do + it 'does not hide issues tab' do allow_any_instance_of(Project).to receive(:external_issue_tracker).and_return(JiraService.new) visit project_path(project) - expect(page).to have_selector(".shortcuts-issues") + expect(page).to have_selector('.shortcuts-issues') + end + end + + context 'When external issue tracker is enabled and issues disabled on project settings' do + it 'hides issues tab' do + project.issues_enabled = false + project.save! + allow_any_instance_of(Project).to receive(:external_issue_tracker).and_return(JiraService.new) + + visit project_path(project) + + expect(page).not_to have_selector('.shortcuts-issues') end end diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb index 033ccf06124..de6dd8fc8a6 100644 --- a/spec/features/projects/pipeline_schedules_spec.rb +++ b/spec/features/projects/pipeline_schedules_spec.rb @@ -70,6 +70,17 @@ feature 'Pipeline Schedules', :feature, js: true do expect(first('.branch-name-cell').text).to eq('') end end + + context 'when ref is empty' do + before do + pipeline_schedule.update_attribute(:ref, '') + visit_pipelines_schedules + end + + it 'shows a list of the pipeline schedules with empty ref column' do + expect(first('.branch-name-cell').text).to eq('') + end + end end describe 'POST /projects/pipeline_schedules/new' do @@ -128,6 +139,19 @@ feature 'Pipeline Schedules', :feature, js: true do end end end + + context 'when ref is empty' do + before do + pipeline_schedule.update_attribute(:ref, '') + edit_pipeline_schedule + end + + it 'shows the pipeline schedule with default ref' do + page.within('.js-target-branch-dropdown') do + expect(first('.dropdown-toggle-text').text).to eq('master') + end + end + end end context 'when user creates a new pipeline schedule with variables' do diff --git a/spec/features/projects/services/mattermost_slash_command_spec.rb b/spec/features/projects/services/mattermost_slash_command_spec.rb index 584d3ed8f42..3319b0fedf3 100644 --- a/spec/features/projects/services/mattermost_slash_command_spec.rb +++ b/spec/features/projects/services/mattermost_slash_command_spec.rb @@ -159,7 +159,7 @@ feature 'Setup Mattermost slash commands', :feature, :js do it 'shows the correct trigger url' do value = find_field('request_url').value - expect(value).to match("api/v3/projects/#{project.id}/services/mattermost_slash_commands/trigger") + expect(value).to match("api/v4/projects/#{project.id}/services/mattermost_slash_commands/trigger") end it 'shows a token placeholder' do diff --git a/spec/features/projects/services/slack_slash_command_spec.rb b/spec/features/projects/services/slack_slash_command_spec.rb index 4efe484262a..71f5a8d7a4e 100644 --- a/spec/features/projects/services/slack_slash_command_spec.rb +++ b/spec/features/projects/services/slack_slash_command_spec.rb @@ -40,6 +40,6 @@ feature 'Slack slash commands', feature: true do it 'shows the correct trigger url' do value = find_field('url').value - expect(value).to match("api/v3/projects/#{project.id}/services/slack_slash_commands/trigger") + expect(value).to match("api/v4/projects/#{project.id}/services/slack_slash_commands/trigger") end end diff --git a/spec/features/projects/settings/integration_settings_spec.rb b/spec/features/projects/settings/integration_settings_spec.rb index 13313bfde24..6ae242af87f 100644 --- a/spec/features/projects/settings/integration_settings_spec.rb +++ b/spec/features/projects/settings/integration_settings_spec.rb @@ -36,14 +36,14 @@ feature 'Integration settings', feature: true do expect(page.status_code).to eq(200) expect(page).to have_content(hook.url) expect(page).to have_content('SSL Verification: enabled') - expect(page).to have_content('Push Events') - expect(page).to have_content('Tag Push Events') - expect(page).to have_content('Issues Events') - expect(page).to have_content('Confidential Issues Events') - expect(page).to have_content('Note Events') - expect(page).to have_content('Merge Requests Events') - expect(page).to have_content('Pipeline Events') - expect(page).to have_content('Wiki Page Events') + expect(page).to have_content('Push events') + expect(page).to have_content('Tag push events') + expect(page).to have_content('Issues events') + expect(page).to have_content('Confidential issues events') + expect(page).to have_content('Note events') + expect(page).to have_content('Merge requests events') + expect(page).to have_content('Pipeline events') + expect(page).to have_content('Wiki page events') end scenario 'create webhook' do @@ -58,8 +58,8 @@ feature 'Integration settings', feature: true do expect(page).to have_content(url) expect(page).to have_content('SSL Verification: enabled') - expect(page).to have_content('Push Events') - expect(page).to have_content('Tag Push Events') + expect(page).to have_content('Push events') + expect(page).to have_content('Tag push events') expect(page).to have_content('Job events') end @@ -76,11 +76,12 @@ feature 'Integration settings', feature: true do expect(page).to have_content(url) end - scenario 'test existing webhook' do + scenario 'test existing webhook', js: true do WebMock.stub_request(:post, hook.url) visit integrations_path - click_link 'Test' + find('.hook-test-button.dropdown').click + click_link 'Push events' expect(current_path).to eq(integrations_path) end diff --git a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb index 5843f18d89f..8188d4c79f4 100644 --- a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb +++ b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb @@ -18,7 +18,7 @@ feature 'User uploads avatar to group', feature: true do visit group_path(group) - expect(page).to have_selector(%Q(img[src$="/uploads/-/system/group/avatar/#{group.id}/dk.png"])) + expect(page).to have_selector(%Q(img[data-src$="/uploads/-/system/group/avatar/#{group.id}/dk.png"])) # Cheating here to verify something that isn't user-facing, but is important expect(group.reload.avatar.file).to exist diff --git a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb index e8171dcaeb0..2628508afe8 100644 --- a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb +++ b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb @@ -16,7 +16,7 @@ feature 'User uploads avatar to profile', feature: true do visit user_path(user) - expect(page).to have_selector(%Q(img[src$="/uploads/-/system/user/avatar/#{user.id}/dk.png"])) + expect(page).to have_selector(%Q(img[data-src$="/uploads/-/system/user/avatar/#{user.id}/dk.png"])) # Cheating here to verify something that isn't user-facing, but is important expect(user.reload.avatar.file).to exist diff --git a/spec/finders/admin/projects_finder_spec.rb b/spec/finders/admin/projects_finder_spec.rb new file mode 100644 index 00000000000..296d6c51d04 --- /dev/null +++ b/spec/finders/admin/projects_finder_spec.rb @@ -0,0 +1,136 @@ +require 'spec_helper' + +describe Admin::ProjectsFinder do + describe '#execute' do + let(:user) { create(:user) } + let(:group) { create(:group, :public) } + + let!(:private_project) do + create(:empty_project, :private, name: 'A', path: 'A') + end + + let!(:internal_project) do + create(:empty_project, :internal, group: group, name: 'B', path: 'B') + end + + let!(:public_project) do + create(:empty_project, :public, group: group, name: 'C', path: 'C') + end + + let!(:shared_project) do + create(:empty_project, :private, name: 'D', path: 'D') + end + + let(:params) { {} } + let(:current_user) { user } + let(:project_ids_relation) { nil } + let(:finder) { described_class.new(params: params, current_user: current_user) } + + subject { finder.execute.to_a } + + context 'without a user' do + let(:current_user) { nil } + + it { is_expected.to match_array([shared_project, public_project, internal_project, private_project]) } + end + + context 'with a user' do + it { is_expected.to match_array([shared_project, public_project, internal_project, private_project]) } + end + + context 'filter by namespace_id' do + let(:namespace) { create(:namespace) } + let!(:project_in_namespace) { create(:empty_project, namespace: namespace) } + let(:params) { { namespace_id: namespace.id } } + + it { is_expected.to eq([project_in_namespace]) } + end + + context 'filter by visibility_level' do + before do + private_project.add_master(user) + end + + context 'private' do + let(:params) { { visibility_level: Gitlab::VisibilityLevel::PRIVATE } } + + it { is_expected.to match_array([shared_project, private_project]) } + end + + context 'internal' do + let(:params) { { visibility_level: Gitlab::VisibilityLevel::INTERNAL } } + + it { is_expected.to eq([internal_project]) } + end + + context 'public' do + let(:params) { { visibility_level: Gitlab::VisibilityLevel::PUBLIC } } + + it { is_expected.to eq([public_project]) } + end + end + + context 'filter by push' do + let(:pushed_event) { create(:event, :pushed) } + let!(:project_with_push) { pushed_event.project } + let(:params) { { with_push: true } } + + it { is_expected.to eq([project_with_push]) } + end + + context 'filter by abandoned' do + before do + private_project.update(last_activity_at: Time.zone.now - 6.months - 1.minute) + end + + let(:params) { { abandoned: true } } + + it { is_expected.to eq([private_project]) } + end + + context 'filter by last_repository_check_failed' do + before do + private_project.update(last_repository_check_failed: true) + end + + let(:params) { { last_repository_check_failed: true } } + + it { is_expected.to eq([private_project]) } + end + + context 'filter by archived' do + let!(:archived_project) { create(:empty_project, :public, :archived, name: 'E', path: 'E') } + + context 'archived=false' do + let(:params) { { archived: false } } + + it { is_expected.to match_array([shared_project, public_project, internal_project, private_project]) } + end + + context 'archived=true' do + let(:params) { { archived: true } } + + it { is_expected.to match_array([archived_project, shared_project, public_project, internal_project, private_project]) } + end + end + + context 'filter by personal' do + let!(:personal_project) { create(:empty_project, namespace: user.namespace) } + let(:params) { { personal: true } } + + it { is_expected.to eq([personal_project]) } + end + + context 'filter by name' do + let(:params) { { name: 'C' } } + + it { is_expected.to match_array([shared_project, public_project, private_project]) } + end + + context 'sorting' do + let(:params) { { sort: 'name_asc' } } + + it { is_expected.to eq([private_project, internal_project, public_project, shared_project]) } + end + end +end diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index f5e139685e8..ac5a58ac189 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -62,13 +62,13 @@ describe ApplicationHelper do avatar_url = "/uploads/-/system/project/avatar/#{project.id}/banana_sample.gif" expect(helper.project_icon(project.full_path).to_s) - .to eq "<img src=\"#{avatar_url}\" alt=\"Banana sample\" />" + .to eq "<img data-src=\"#{avatar_url}\" class=\" lazy\" src=\"#{LazyImageTagHelper.placeholder_image}\" />" allow(ActionController::Base).to receive(:asset_host).and_return(gitlab_host) avatar_url = "#{gitlab_host}/uploads/-/system/project/avatar/#{project.id}/banana_sample.gif" expect(helper.project_icon(project.full_path).to_s) - .to eq "<img src=\"#{avatar_url}\" alt=\"Banana sample\" />" + .to eq "<img data-src=\"#{avatar_url}\" class=\" lazy\" src=\"#{LazyImageTagHelper.placeholder_image}\" />" end it 'gives uploaded icon when present' do @@ -77,7 +77,8 @@ describe ApplicationHelper do allow_any_instance_of(Project).to receive(:avatar_in_git).and_return(true) avatar_url = "#{gitlab_host}#{project_avatar_path(project)}" - expect(helper.project_icon(project.full_path).to_s).to match(image_tag(avatar_url)) + expect(helper.project_icon(project.full_path).to_s) + .to eq "<img data-src=\"#{avatar_url}\" class=\" lazy\" src=\"#{LazyImageTagHelper.placeholder_image}\" />" end end diff --git a/spec/helpers/avatars_helper_spec.rb b/spec/helpers/avatars_helper_spec.rb index 049475a5408..d16fcf21e45 100644 --- a/spec/helpers/avatars_helper_spec.rb +++ b/spec/helpers/avatars_helper_spec.rb @@ -27,11 +27,11 @@ describe AvatarsHelper do it 'displays user avatar' do is_expected.to eq image_tag( - avatar_icon(user, 16), - class: 'avatar has-tooltip s16 ', + LazyImageTagHelper.placeholder_image, + class: 'avatar has-tooltip s16 lazy', alt: "#{user.name}'s avatar", title: user.name, - data: { container: 'body' } + data: { container: 'body', src: avatar_icon(user, 16) } ) end @@ -40,22 +40,8 @@ describe AvatarsHelper do it 'uses provided css_class' do is_expected.to eq image_tag( - avatar_icon(user, 16), - class: "avatar has-tooltip s16 #{options[:css_class]}", - alt: "#{user.name}'s avatar", - title: user.name, - data: { container: 'body' } - ) - end - end - - context 'with lazy parameter' do - let(:options) { { user: user, lazy: true } } - - it 'uses data-src instead of src' do - is_expected.to eq image_tag( - '', - class: 'avatar has-tooltip s16 ', + LazyImageTagHelper.placeholder_image, + class: "avatar has-tooltip s16 #{options[:css_class]} lazy", alt: "#{user.name}'s avatar", title: user.name, data: { container: 'body', src: avatar_icon(user, 16) } @@ -68,11 +54,11 @@ describe AvatarsHelper do it 'uses provided size' do is_expected.to eq image_tag( - avatar_icon(user, options[:size]), - class: "avatar has-tooltip s#{options[:size]} ", + LazyImageTagHelper.placeholder_image, + class: "avatar has-tooltip s#{options[:size]} lazy", alt: "#{user.name}'s avatar", title: user.name, - data: { container: 'body' } + data: { container: 'body', src: avatar_icon(user, options[:size]) } ) end end @@ -82,11 +68,11 @@ describe AvatarsHelper do it 'uses provided url' do is_expected.to eq image_tag( - options[:url], - class: 'avatar has-tooltip s16 ', + LazyImageTagHelper.placeholder_image, + class: 'avatar has-tooltip s16 lazy', alt: "#{user.name}'s avatar", title: user.name, - data: { container: 'body' } + data: { container: 'body', src: options[:url] } ) end end @@ -99,22 +85,22 @@ describe AvatarsHelper do it 'prefers user parameter' do is_expected.to eq image_tag( - avatar_icon(user, 16), - class: 'avatar has-tooltip s16 ', + LazyImageTagHelper.placeholder_image, + class: 'avatar has-tooltip s16 lazy', alt: "#{user.name}'s avatar", title: user.name, - data: { container: 'body' } + data: { container: 'body', src: avatar_icon(user, 16) } ) end end it 'uses user_name and user_email parameter if user is not present' do is_expected.to eq image_tag( - avatar_icon(options[:user_email], 16), - class: 'avatar has-tooltip s16 ', + LazyImageTagHelper.placeholder_image, + class: 'avatar has-tooltip s16 lazy', alt: "#{options[:user_name]}'s avatar", title: options[:user_name], - data: { container: 'body' } + data: { container: 'body', src: avatar_icon(options[:user_email], 16) } ) end end diff --git a/spec/helpers/hooks_helper_spec.rb b/spec/helpers/hooks_helper_spec.rb new file mode 100644 index 00000000000..9f0004bf8cf --- /dev/null +++ b/spec/helpers/hooks_helper_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe HooksHelper do + let(:project) { create(:empty_project) } + let(:project_hook) { create(:project_hook, project: project) } + let(:system_hook) { create(:system_hook) } + let(:trigger) { 'push_events' } + + describe '#link_to_test_hook' do + it 'returns project namespaced link' do + expect(helper.link_to_test_hook(project_hook, trigger)) + .to include("href=\"#{test_project_hook_path(project, project_hook, trigger: trigger)}\"") + end + + it 'returns admin namespaced link' do + expect(helper.link_to_test_hook(system_hook, trigger)) + .to include("href=\"#{test_admin_hook_path(system_hook, trigger: trigger)}\"") + end + end +end diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index 8f7f17a484f..9524a101e74 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -8,7 +8,7 @@ describe IssuesHelper do describe "url_for_issue" do let(:issues_url) { ext_project.external_issue_tracker.issues_url} let(:ext_expected) { issues_url.gsub(':id', issue.iid.to_s).gsub(':project_id', ext_project.id.to_s) } - let(:int_expected) { polymorphic_path([@project.namespace, project, issue]) } + let(:int_expected) { polymorphic_path([@project.namespace, @project, issue]) } it "returns internal path if used internal tracker" do @project = project @@ -22,6 +22,12 @@ describe IssuesHelper do expect(url_for_issue(issue.iid)).to match(ext_expected) end + it "returns path to internal issue when internal option passed" do + @project = ext_project + + expect(url_for_issue(issue.iid, ext_project, internal: true)).to match(int_expected) + end + it "returns empty string if project nil" do @project = nil diff --git a/spec/javascripts/filtered_search/dropdown_user_spec.js b/spec/javascripts/filtered_search/dropdown_user_spec.js index 0132f4b7c93..b3c9bca64cc 100644 --- a/spec/javascripts/filtered_search/dropdown_user_spec.js +++ b/spec/javascripts/filtered_search/dropdown_user_spec.js @@ -12,7 +12,9 @@ describe('Dropdown User', () => { spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {}); spyOn(gl.DropdownUtils, 'getSearchInput').and.callFake(() => {}); - dropdownUser = new gl.DropdownUser(null, null, null, gl.FilteredSearchTokenKeys); + dropdownUser = new gl.DropdownUser({ + tokenKeys: gl.FilteredSearchTokenKeys, + }); }); it('should not return the double quote found in value', () => { @@ -78,7 +80,10 @@ describe('Dropdown User', () => { loadFixtures(fixtureTemplate); authorFilterDropdownElement = document.querySelector('#js-dropdown-author'); const dummyInput = document.createElement('div'); - dropdown = new gl.DropdownUser(null, authorFilterDropdownElement, dummyInput); + dropdown = new gl.DropdownUser({ + dropdown: authorFilterDropdownElement, + input: dummyInput, + }); }); const findCurrentUserElement = () => authorFilterDropdownElement.querySelector('.js-current-user'); diff --git a/spec/javascripts/fixtures/u2f.rb b/spec/javascripts/fixtures/u2f.rb index c9c0b891237..e3d7986f2cf 100644 --- a/spec/javascripts/fixtures/u2f.rb +++ b/spec/javascripts/fixtures/u2f.rb @@ -10,10 +10,12 @@ context 'U2F' do end describe SessionsController, '(JavaScript fixtures)', type: :controller do + include DeviseHelpers + render_views before do - @request.env['devise.mapping'] = Devise.mappings[:user] + set_devise_mapping(context: @request) end it 'u2f/authenticate.html.raw' do |example| diff --git a/spec/javascripts/lazy_loader_spec.js b/spec/javascripts/lazy_loader_spec.js new file mode 100644 index 00000000000..1d81e4e2d1a --- /dev/null +++ b/spec/javascripts/lazy_loader_spec.js @@ -0,0 +1,57 @@ +import LazyLoader from '~/lazy_loader'; + +let lazyLoader = null; + +describe('LazyLoader', function () { + preloadFixtures('issues/issue_with_comment.html.raw'); + + beforeEach(function () { + loadFixtures('issues/issue_with_comment.html.raw'); + lazyLoader = new LazyLoader({ + observerNode: 'body', + }); + // Doing everything that happens normally in onload + lazyLoader.loadCheck(); + }); + describe('behavior', function () { + it('should copy value from data-src to src for img 1', function (done) { + const img = document.querySelectorAll('img[data-src]')[0]; + const originalDataSrc = img.getAttribute('data-src'); + img.scrollIntoView(); + + setTimeout(() => { + expect(img.getAttribute('src')).toBe(originalDataSrc); + expect(document.getElementsByClassName('js-lazy-loaded').length).toBeGreaterThan(0); + done(); + }, 100); + }); + + it('should lazy load dynamically added data-src images', function (done) { + const newImg = document.createElement('img'); + const testPath = '/img/testimg.png'; + newImg.className = 'lazy'; + newImg.setAttribute('data-src', testPath); + document.body.appendChild(newImg); + newImg.scrollIntoView(); + + setTimeout(() => { + expect(newImg.getAttribute('src')).toBe(testPath); + expect(document.getElementsByClassName('js-lazy-loaded').length).toBeGreaterThan(0); + done(); + }, 100); + }); + + it('should not alter normal images', function (done) { + const newImg = document.createElement('img'); + const testPath = '/img/testimg.png'; + newImg.setAttribute('src', testPath); + document.body.appendChild(newImg); + newImg.scrollIntoView(); + + setTimeout(() => { + expect(newImg).not.toHaveClass('js-lazy-loaded'); + done(); + }, 100); + }); + }); +}); diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb index b7d82c36ddd..fb320e0148a 100644 --- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb @@ -108,6 +108,11 @@ describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true do let(:issue) { ExternalIssue.new("#123", project) } let(:reference) { issue.to_reference } + before do + project.issues_enabled = false + project.save! + end + it_behaves_like "external issue tracker" end diff --git a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb index 082c0d4dd0d..cbb2808c6bb 100644 --- a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb +++ b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb @@ -22,7 +22,7 @@ describe Banzai::Filter::GollumTagsFilter, lib: true do tag = '[[images/image.jpg]]' doc = filter("See #{tag}", project_wiki: project_wiki) - expect(doc.at_css('img')['src']).to eq "#{project_wiki.wiki_base_path}/images/image.jpg" + expect(doc.at_css('img')['data-src']).to eq "#{project_wiki.wiki_base_path}/images/image.jpg" end it 'does not creates img tag if image does not exist' do @@ -40,7 +40,7 @@ describe Banzai::Filter::GollumTagsFilter, lib: true do tag = '[[http://example.com/image.jpg]]' doc = filter("See #{tag}", project_wiki: project_wiki) - expect(doc.at_css('img')['src']).to eq "http://example.com/image.jpg" + expect(doc.at_css('img')['data-src']).to eq "http://example.com/image.jpg" end it 'does not creates img tag for invalid URL' do diff --git a/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb b/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb new file mode 100644 index 00000000000..c19de7b784a --- /dev/null +++ b/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe Banzai::Filter::ImageLazyLoadFilter, lib: true do + include FilterSpecHelper + + def image(path) + %(<img src="#{path}" />) + end + + it 'transforms the image src to a data-src' do + doc = filter(image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')) + expect(doc.at_css('img')['data-src']).to eq '/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg' + end + + it 'works with external images' do + doc = filter(image('https://i.imgur.com/DfssX9C.jpg')) + expect(doc.at_css('img')['data-src']).to eq 'https://i.imgur.com/DfssX9C.jpg' + end +end diff --git a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb index 1eb90dc1847..601ffbb5456 100644 --- a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb @@ -4,26 +4,87 @@ describe Banzai::Pipeline::GfmPipeline do describe 'integration between parsing regular and external issue references' do let(:project) { create(:redmine_project, :public) } - it 'allows to use shorthand external reference syntax for Redmine' do - markdown = '#12' + context 'when internal issue tracker is enabled' do + context 'when shorthand pattern #ISSUE_ID is used' do + it 'links an internal issue if it exists' do + issue = create(:issue, project: project) + markdown = issue.to_reference(project, full: true) - result = described_class.call(markdown, project: project)[:output] - link = result.css('a').first + result = described_class.call(markdown, project: project)[:output] + link = result.css('a').first - expect(link['href']).to eq 'http://redmine/projects/project_name_in_redmine/issues/12' + expect(link['href']).to eq( + Gitlab::Routing.url_helpers.project_issue_path(project, issue) + ) + end + + it 'does not link any issue if it does not exist on GitLab' do + markdown = '#12' + + result = described_class.call(markdown, project: project)[:output] + expect(result.css('a')).to be_empty + end + end + + it 'allows to use long external reference syntax for Redmine' do + markdown = 'API_32-12' + + result = described_class.call(markdown, project: project)[:output] + link = result.css('a').first + + expect(link['href']).to eq 'http://redmine/projects/project_name_in_redmine/issues/12' + end + + it 'parses cross-project references to regular issues' do + other_project = create(:empty_project, :public) + issue = create(:issue, project: other_project) + markdown = issue.to_reference(project, full: true) + + result = described_class.call(markdown, project: project)[:output] + link = result.css('a').first + + expect(link['href']).to eq( + Gitlab::Routing.url_helpers.project_issue_path(other_project, issue) + ) + end end - it 'parses cross-project references to regular issues' do - other_project = create(:empty_project, :public) - issue = create(:issue, project: other_project) - markdown = issue.to_reference(project, full: true) + context 'when internal issue tracker is disabled' do + before do + project.issues_enabled = false + project.save! + end + + it 'allows to use shorthand external reference syntax for Redmine' do + markdown = '#12' + + result = described_class.call(markdown, project: project)[:output] + link = result.css('a').first + + expect(link['href']).to eq 'http://redmine/projects/project_name_in_redmine/issues/12' + end + + it 'allows to use long external reference syntax for Redmine' do + markdown = 'API_32-12' + + result = described_class.call(markdown, project: project)[:output] + link = result.css('a').first + + expect(link['href']).to eq 'http://redmine/projects/project_name_in_redmine/issues/12' + end + + it 'parses cross-project references to regular issues' do + other_project = create(:empty_project, :public) + issue = create(:issue, project: other_project) + markdown = issue.to_reference(project, full: true) - result = described_class.call(markdown, project: project)[:output] - link = result.css('a').first + result = described_class.call(markdown, project: project)[:output] + link = result.css('a').first - expect(link['href']).to eq( - Gitlab::Routing.url_helpers.project_issue_path(other_project, issue) - ) + expect(link['href']).to eq( + Gitlab::Routing.url_helpers.project_issue_path(other_project, issue) + ) + end end end end diff --git a/spec/lib/gitlab/badge/build/metadata_spec.rb b/spec/lib/gitlab/badge/pipeline/metadata_spec.rb index 9df96ea04eb..d537ce8803c 100644 --- a/spec/lib/gitlab/badge/build/metadata_spec.rb +++ b/spec/lib/gitlab/badge/pipeline/metadata_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' require 'lib/gitlab/badge/shared/metadata' -describe Gitlab::Badge::Build::Metadata do +describe Gitlab::Badge::Pipeline::Metadata do let(:badge) { double(project: create(:empty_project), ref: 'feature') } let(:metadata) { described_class.new(badge) } @@ -9,13 +9,13 @@ describe Gitlab::Badge::Build::Metadata do describe '#title' do it 'returns build status title' do - expect(metadata.title).to eq 'build status' + expect(metadata.title).to eq 'pipeline status' end end describe '#image_url' do it 'returns valid url' do - expect(metadata.image_url).to include 'badges/feature/build.svg' + expect(metadata.image_url).to include 'badges/feature/pipeline.svg' end end diff --git a/spec/lib/gitlab/badge/build/status_spec.rb b/spec/lib/gitlab/badge/pipeline/status_spec.rb index 6abf4ca46a9..dc835375c66 100644 --- a/spec/lib/gitlab/badge/build/status_spec.rb +++ b/spec/lib/gitlab/badge/pipeline/status_spec.rb @@ -1,36 +1,35 @@ require 'spec_helper' -describe Gitlab::Badge::Build::Status do +describe Gitlab::Badge::Pipeline::Status do let(:project) { create(:project, :repository) } let(:sha) { project.commit.sha } let(:branch) { 'master' } let(:badge) { described_class.new(project, branch) } describe '#entity' do - it 'always says build' do - expect(badge.entity).to eq 'build' + it 'always says pipeline' do + expect(badge.entity).to eq 'pipeline' end end describe '#template' do it 'returns badge template' do - expect(badge.template.key_text).to eq 'build' + expect(badge.template.key_text).to eq 'pipeline' end end describe '#metadata' do it 'returns badge metadata' do - expect(badge.metadata.image_url) - .to include 'badges/master/build.svg' + expect(badge.metadata.image_url).to include 'badges/master/pipeline.svg' end end - context 'build exists' do - let!(:build) { create_build(project, sha, branch) } + context 'pipeline exists' do + let!(:pipeline) { create_pipeline(project, sha, branch) } - context 'build success' do + context 'pipeline success' do before do - build.success! + pipeline.success! end describe '#status' do @@ -40,9 +39,9 @@ describe Gitlab::Badge::Build::Status do end end - context 'build failed' do + context 'pipeline failed' do before do - build.drop! + pipeline.drop! end describe '#status' do @@ -54,10 +53,10 @@ describe Gitlab::Badge::Build::Status do context 'when outdated pipeline for given ref exists' do before do - build.success! + pipeline.success! - old_build = create_build(project, '11eeffdd', branch) - old_build.drop! + old_pipeline = create_pipeline(project, '11eeffdd', branch) + old_pipeline.drop! end it 'does not take outdated pipeline into account' do @@ -67,10 +66,10 @@ describe Gitlab::Badge::Build::Status do context 'when multiple pipelines exist for given sha' do before do - build.drop! + pipeline.drop! - new_build = create_build(project, sha, branch) - new_build.success! + new_pipeline = create_pipeline(project, sha, branch) + new_pipeline.success! end it 'does not take outdated pipeline into account' do @@ -87,7 +86,7 @@ describe Gitlab::Badge::Build::Status do end end - def create_build(project, sha, branch) + def create_pipeline(project, sha, branch) pipeline = create(:ci_empty_pipeline, project: project, sha: sha, diff --git a/spec/lib/gitlab/badge/build/template_spec.rb b/spec/lib/gitlab/badge/pipeline/template_spec.rb index a7e21fb8bb1..20fa4f879c3 100644 --- a/spec/lib/gitlab/badge/build/template_spec.rb +++ b/spec/lib/gitlab/badge/pipeline/template_spec.rb @@ -1,28 +1,28 @@ require 'spec_helper' -describe Gitlab::Badge::Build::Template do - let(:badge) { double(entity: 'build', status: 'success') } +describe Gitlab::Badge::Pipeline::Template do + let(:badge) { double(entity: 'pipeline', status: 'success') } let(:template) { described_class.new(badge) } describe '#key_text' do - it 'is always says build' do - expect(template.key_text).to eq 'build' + it 'is always says pipeline' do + expect(template.key_text).to eq 'pipeline' end end describe '#value_text' do it 'is status value' do - expect(template.value_text).to eq 'success' + expect(template.value_text).to eq 'passed' end end describe 'widths and text anchors' do it 'has fixed width and text anchors' do - expect(template.width).to eq 92 - expect(template.key_width).to eq 38 + expect(template.width).to eq 116 + expect(template.key_width).to eq 62 expect(template.value_width).to eq 54 - expect(template.key_text_anchor).to eq 19 - expect(template.value_text_anchor).to eq 65 + expect(template.key_text_anchor).to eq 31 + expect(template.value_text_anchor).to eq 89 end end diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb index 13f0338b6aa..ebe5af56160 100644 --- a/spec/lib/gitlab/ci/trace/stream_spec.rb +++ b/spec/lib/gitlab/ci/trace/stream_spec.rb @@ -300,5 +300,48 @@ describe Gitlab::Ci::Trace::Stream do include_examples 'malicious regexp' end + + context 'multi-line data with rooted regexp' do + let(:data) { "\n65%\n" } + let(:regex) { '^(\d+)\%$' } + + it { is_expected.to eq('65') } + end + + context 'long line' do + let(:data) { 'a' * 80000 + '100%' + 'a' * 80000 } + let(:regex) { '\d+\%' } + + it { is_expected.to eq('100') } + end + + context 'many lines' do + let(:data) { "foo\n" * 80000 + "100%\n" + "foo\n" * 80000 } + let(:regex) { '\d+\%' } + + it { is_expected.to eq('100') } + end + + context 'empty regex' do + let(:data) { 'foo' } + let(:regex) { '' } + + it 'skips processing' do + expect(stream).not_to receive(:read) + + is_expected.to be_nil + end + end + + context 'nil regex' do + let(:data) { 'foo' } + let(:regex) { nil } + + it 'skips processing' do + expect(stream).not_to receive(:read) + + is_expected.to be_nil + end + end end end diff --git a/spec/lib/gitlab/data_builder/wiki_page_spec.rb b/spec/lib/gitlab/data_builder/wiki_page_spec.rb new file mode 100644 index 00000000000..a776d888c47 --- /dev/null +++ b/spec/lib/gitlab/data_builder/wiki_page_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe Gitlab::DataBuilder::WikiPage do + let(:project) { create(:project, :repository) } + let(:wiki_page) { create(:wiki_page, wiki: project.wiki) } + let(:user) { create(:user) } + + describe '.build' do + let(:data) { described_class.build(wiki_page, user, 'create') } + + it { expect(data).to be_a(Hash) } + it { expect(data[:object_kind]).to eq('wiki_page') } + it { expect(data[:user]).to eq(user.hook_attrs) } + it { expect(data[:project]).to eq(project.hook_attrs) } + it { expect(data[:wiki]).to eq(project.wiki.hook_attrs) } + + it { expect(data[:object_attributes]).to include(wiki_page.hook_attrs) } + it { expect(data[:object_attributes]).to include(url: Gitlab::UrlBuilder.build(wiki_page)) } + it { expect(data[:object_attributes]).to include(action: 'create') } + end +end diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index 60de91324f0..730fdb112d9 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -91,7 +91,7 @@ describe Gitlab::Git::Commit, seed_helper: true do committer: committer ) end - let(:commit) { described_class.new(gitaly_commit) } + let(:commit) { described_class.new(Gitlab::GitalyClient::Commit.new(repository, gitaly_commit)) } it { expect(commit.short_id).to eq(id[0..10]) } it { expect(commit.id).to eq(id) } @@ -290,69 +290,85 @@ describe Gitlab::Git::Commit, seed_helper: true do end describe '.find_all' do - it 'should return a return a collection of commits' do - commits = described_class.find_all(repository) + shared_examples 'finding all commits' do + it 'should return a return a collection of commits' do + commits = described_class.find_all(repository) - expect(commits).not_to be_empty - expect(commits).to all( be_a_kind_of(Gitlab::Git::Commit) ) - end - - context 'while applying a sort order based on the `order` option' do - it "allows ordering topologically (no parents shown before their children)" do - expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_TOPO) - - described_class.find_all(repository, order: :topo) + expect(commits).to all( be_a_kind_of(Gitlab::Git::Commit) ) end - it "allows ordering by date" do - expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_DATE | Rugged::SORT_TOPO) - - described_class.find_all(repository, order: :date) + context 'max_count' do + subject do + commits = Gitlab::Git::Commit.find_all( + repository, + max_count: 50 + ) + + commits.map(&:id) + end + + it 'has 33 elements' do + expect(subject.size).to eq(33) + end + + it 'includes the expected commits' do + expect(subject).to include( + SeedRepo::Commit::ID, + SeedRepo::Commit::PARENT_ID, + SeedRepo::FirstCommit::ID + ) + end end - it "applies no sorting by default" do - expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_NONE) - - described_class.find_all(repository) + context 'ref + max_count + skip' do + subject do + commits = Gitlab::Git::Commit.find_all( + repository, + ref: 'master', + max_count: 50, + skip: 1 + ) + + commits.map(&:id) + end + + it 'has 24 elements' do + expect(subject.size).to eq(24) + end + + it 'includes the expected commits' do + expect(subject).to include(SeedRepo::Commit::ID, SeedRepo::FirstCommit::ID) + expect(subject).not_to include(SeedRepo::LastCommit::ID) + end end end - context 'max_count' do - subject do - commits = Gitlab::Git::Commit.find_all( - repository, - max_count: 50 - ) + context 'when Gitaly find_all_commits feature is enabled' do + it_behaves_like 'finding all commits' + end - commits.map { |c| c.id } - end + context 'when Gitaly find_all_commits feature is disabled', skip_gitaly_mock: true do + it_behaves_like 'finding all commits' - it 'has 31 elements' do - expect(subject.size).to eq(33) - end - it { is_expected.to include(SeedRepo::Commit::ID) } - it { is_expected.to include(SeedRepo::Commit::PARENT_ID) } - it { is_expected.to include(SeedRepo::FirstCommit::ID) } - end + context 'while applying a sort order based on the `order` option' do + it "allows ordering topologically (no parents shown before their children)" do + expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_TOPO) - context 'ref + max_count + skip' do - subject do - commits = Gitlab::Git::Commit.find_all( - repository, - ref: 'master', - max_count: 50, - skip: 1 - ) + described_class.find_all(repository, order: :topo) + end - commits.map { |c| c.id } - end + it "allows ordering by date" do + expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_DATE | Rugged::SORT_TOPO) + + described_class.find_all(repository, order: :date) + end + + it "applies no sorting by default" do + expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_NONE) - it 'has 23 elements' do - expect(subject.size).to eq(24) + described_class.find_all(repository) + end end - it { is_expected.to include(SeedRepo::Commit::ID) } - it { is_expected.to include(SeedRepo::FirstCommit::ID) } - it { is_expected.not_to include(SeedRepo::LastCommit::ID) } end end end diff --git a/spec/lib/gitlab/git/diff_collection_spec.rb b/spec/lib/gitlab/git/diff_collection_spec.rb index d20298fa139..0cfb210e390 100644 --- a/spec/lib/gitlab/git/diff_collection_spec.rb +++ b/spec/lib/gitlab/git/diff_collection_spec.rb @@ -484,6 +484,8 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do end def each + return enum_for(:each) unless block_given? + loop do break if @count.zero? # It is critical to decrement before yielding. We may never reach the lines after 'yield'. diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb index 4b76a43e6b5..98ddd3c3664 100644 --- a/spec/lib/gitlab/git/tree_spec.rb +++ b/spec/lib/gitlab/git/tree_spec.rb @@ -1,8 +1,9 @@ require "spec_helper" describe Gitlab::Git::Tree, seed_helper: true do + let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) } + context :repo do - let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) } let(:tree) { Gitlab::Git::Tree.where(repository, SeedRepo::Commit::ID) } it { expect(tree).to be_kind_of Array } @@ -74,4 +75,24 @@ describe Gitlab::Git::Tree, seed_helper: true do it { expect(submodule.name).to eq('gitlab-shell') } end end + + describe '#where' do + context 'with gitaly disabled' do + before do + allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(false) + end + + it 'calls #tree_entries_from_rugged' do + expect(described_class).to receive(:tree_entries_from_rugged) + + described_class.where(repository, SeedRepo::Commit::ID, '/') + end + end + + it 'gets the tree entries from GitalyClient' do + expect_any_instance_of(Gitlab::GitalyClient::CommitService).to receive(:tree_entries) + + described_class.where(repository, SeedRepo::Commit::ID, '/') + end + end end diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb index 93affb12f2b..0868c793a33 100644 --- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb @@ -2,9 +2,13 @@ require 'spec_helper' describe Gitlab::GitalyClient::CommitService do let(:project) { create(:project, :repository) } + let(:storage_name) { project.repository_storage } + let(:relative_path) { project.path_with_namespace + '.git' } let(:repository) { project.repository } let(:repository_message) { repository.gitaly_repository } - let(:commit) { project.commit('913c66a37b4a45b9769037c55c2d238bd0942d2e') } + let(:revision) { '913c66a37b4a45b9769037c55c2d238bd0942d2e' } + let(:commit) { project.commit(revision) } + let(:client) { described_class.new(repository) } describe '#diff_from_parent' do context 'when a commit has a parent' do @@ -12,12 +16,15 @@ describe Gitlab::GitalyClient::CommitService do request = Gitaly::CommitDiffRequest.new( repository: repository_message, left_commit_id: 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660', - right_commit_id: commit.id + right_commit_id: commit.id, + collapse_diffs: true, + enforce_limits: true, + **Gitlab::Git::DiffCollection.collection_limits.to_h ) expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_diff).with(request, kind_of(Hash)) - described_class.new(repository).diff_from_parent(commit) + client.diff_from_parent(commit) end end @@ -27,27 +34,30 @@ describe Gitlab::GitalyClient::CommitService do request = Gitaly::CommitDiffRequest.new( repository: repository_message, left_commit_id: '4b825dc642cb6eb9a060e54bf8d69288fbee4904', - right_commit_id: initial_commit.id + right_commit_id: initial_commit.id, + collapse_diffs: true, + enforce_limits: true, + **Gitlab::Git::DiffCollection.collection_limits.to_h ) expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_diff).with(request, kind_of(Hash)) - described_class.new(repository).diff_from_parent(initial_commit) + client.diff_from_parent(initial_commit) end end it 'returns a Gitlab::Git::DiffCollection' do - ret = described_class.new(repository).diff_from_parent(commit) + ret = client.diff_from_parent(commit) expect(ret).to be_kind_of(Gitlab::Git::DiffCollection) end it 'passes options to Gitlab::Git::DiffCollection' do - options = { max_files: 31, max_lines: 13 } + options = { max_files: 31, max_lines: 13, from_gitaly: true } expect(Gitlab::Git::DiffCollection).to receive(:new).with(kind_of(Enumerable), options) - described_class.new(repository).diff_from_parent(commit, options) + client.diff_from_parent(commit, options) end end @@ -62,7 +72,7 @@ describe Gitlab::GitalyClient::CommitService do expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_delta).with(request, kind_of(Hash)).and_return([]) - described_class.new(repository).commit_deltas(commit) + client.commit_deltas(commit) end end @@ -77,7 +87,7 @@ describe Gitlab::GitalyClient::CommitService do expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_delta).with(request, kind_of(Hash)).and_return([]) - described_class.new(repository).commit_deltas(initial_commit) + client.commit_deltas(initial_commit) end end end @@ -85,6 +95,7 @@ describe Gitlab::GitalyClient::CommitService do describe '#between' do let(:from) { 'master' } let(:to) { '4b825dc642cb6eb9a060e54bf8d69288fbee4904' } + it 'sends an RPC request' do request = Gitaly::CommitsBetweenRequest.new( repository: repository_message, from: from, to: to @@ -96,4 +107,17 @@ describe Gitlab::GitalyClient::CommitService do described_class.new(repository).between(from, to) end end + + describe '#tree_entries' do + let(:path) { '/' } + + it 'sends a get_tree_entries message' do + expect_any_instance_of(Gitaly::CommitService::Stub) + .to receive(:get_tree_entries) + .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash)) + .and_return([]) + + client.tree_entries(repository, revision, path) + end + end end diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb index 1eea710c80b..20be743d224 100644 --- a/spec/lib/gitlab/path_regex_spec.rb +++ b/spec/lib/gitlab/path_regex_spec.rb @@ -36,9 +36,12 @@ describe Gitlab::PathRegex, lib: true do described_class::PROJECT_WILDCARD_ROUTES.include?(path.split('/').first) end - def failure_message(missing_words, constant_name, migration_helper) + def failure_message(constant_name, migration_helper, missing_words: [], additional_words: []) missing_words = Array(missing_words) - <<-MSG + additional_words = Array(additional_words) + message = "" + if missing_words.any? + message += <<-MISSING Found new routes that could cause conflicts with existing namespaced routes for groups or projects. @@ -51,7 +54,18 @@ describe Gitlab::PathRegex, lib: true do Make sure to make a note of the renamed records in the release blog post. - MSG + MISSING + end + + if additional_words.any? + message += <<-ADDITIONAL + Why are <#{additional_words.join(', ')}> in `#{constant_name}`? + If they are really required, update these specs to reflect that. + + ADDITIONAL + end + + message end let(:all_routes) do @@ -68,9 +82,23 @@ describe Gitlab::PathRegex, lib: true do let(:routes_not_starting_in_wildcard) { routes_without_format.select { |p| p !~ %r{^/[:*]} } } let(:top_level_words) do - routes_not_starting_in_wildcard.map do |route| + words = routes_not_starting_in_wildcard.map do |route| route.split('/')[1] end.compact.uniq + + words + ee_top_level_words + files_in_public + Array(API::API.prefix.to_s) + end + + let(:ee_top_level_words) do + ['unsubscribes'] + end + + let(:files_in_public) do + git = Gitlab.config.git.bin_path + `cd #{Rails.root} && #{git} ls-files public` + .split("\n") + .map { |entry| entry.gsub('public/', '') } + .uniq end # All routes that start with a namespaced path, that have 1 or more @@ -115,18 +143,29 @@ describe Gitlab::PathRegex, lib: true do let(:paths_after_group_id) do group_routes.map do |route| route.gsub(STARTING_WITH_GROUP, '').split('/').first - end.uniq + end.uniq + ee_paths_after_group_id + end + + let(:ee_paths_after_group_id) do + %w(analytics + ldap + ldap_group_links + notification_setting + audit_events + pipeline_quota hooks) end describe 'TOP_LEVEL_ROUTES' do it 'includes all the top level namespaces' do failure_block = lambda do missing_words = top_level_words - described_class::TOP_LEVEL_ROUTES - failure_message(missing_words, 'TOP_LEVEL_ROUTES', 'rename_root_paths') + additional_words = described_class::TOP_LEVEL_ROUTES - top_level_words + failure_message('TOP_LEVEL_ROUTES', 'rename_root_paths', + missing_words: missing_words, additional_words: additional_words) end expect(described_class::TOP_LEVEL_ROUTES) - .to include(*top_level_words), failure_block + .to contain_exactly(*top_level_words), failure_block end end @@ -134,11 +173,13 @@ describe Gitlab::PathRegex, lib: true do it "don't contain a second wildcard" do failure_block = lambda do missing_words = paths_after_group_id - described_class::GROUP_ROUTES - failure_message(missing_words, 'GROUP_ROUTES', 'rename_child_paths') + additional_words = described_class::GROUP_ROUTES - paths_after_group_id + failure_message('GROUP_ROUTES', 'rename_child_paths', + missing_words: missing_words, additional_words: additional_words) end expect(described_class::GROUP_ROUTES) - .to include(*paths_after_group_id), failure_block + .to contain_exactly(*paths_after_group_id), failure_block end end @@ -147,7 +188,7 @@ describe Gitlab::PathRegex, lib: true do aggregate_failures do all_wildcard_paths.each do |path| expect(wildcards_include?(path)) - .to be(true), failure_message(path, 'PROJECT_WILDCARD_ROUTES', 'rename_wildcard_paths') + .to be(true), failure_message('PROJECT_WILDCARD_ROUTES', 'rename_wildcard_paths', missing_words: path) end end end diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb index 84cfd934fa0..917692e9c6c 100644 --- a/spec/lib/gitlab/reference_extractor_spec.rb +++ b/spec/lib/gitlab/reference_extractor_spec.rb @@ -183,11 +183,34 @@ describe Gitlab::ReferenceExtractor, lib: true do context 'with an external issue tracker' do let(:project) { create(:jira_project) } + let(:issue) { create(:issue, project: project) } + + context 'when GitLab issues are enabled' do + it 'returns both JIRA and internal issues' do + subject.analyze("JIRA-123 and FOOBAR-4567 and #{issue.to_reference}") + expect(subject.issues).to eq [ExternalIssue.new('JIRA-123', project), + ExternalIssue.new('FOOBAR-4567', project), + issue] + end + + it 'returns only JIRA issues if the internal one does not exists' do + subject.analyze("JIRA-123 and FOOBAR-4567 and #999") + expect(subject.issues).to eq [ExternalIssue.new('JIRA-123', project), + ExternalIssue.new('FOOBAR-4567', project)] + end + end - it 'returns JIRA issues for a JIRA-integrated project' do - subject.analyze('JIRA-123 and FOOBAR-4567') - expect(subject.issues).to eq [ExternalIssue.new('JIRA-123', project), - ExternalIssue.new('FOOBAR-4567', project)] + context 'when GitLab issues are disabled' do + before do + project.issues_enabled = false + project.save! + end + + it 'returns only JIRA issues' do + subject.analyze("JIRA-123 and FOOBAR-4567 and #{issue.to_reference}") + expect(subject.issues).to eq [ExternalIssue.new('JIRA-123', project), + ExternalIssue.new('FOOBAR-4567', project)] + end end end diff --git a/spec/lib/gitlab/untrusted_regexp_spec.rb b/spec/lib/gitlab/untrusted_regexp_spec.rb index 66045917cb3..bed58d407ef 100644 --- a/spec/lib/gitlab/untrusted_regexp_spec.rb +++ b/spec/lib/gitlab/untrusted_regexp_spec.rb @@ -46,10 +46,28 @@ describe Gitlab::UntrustedRegexp do context 'malicious regexp' do let(:text) { malicious_text } let(:regexp) { malicious_regexp } - + include_examples 'malicious regexp' end + context 'empty regexp' do + let(:regexp) { '' } + let(:text) { 'foo' } + + it 'returns an array of nil matches' do + is_expected.to eq([nil, nil, nil, nil]) + end + end + + context 'empty capture group regexp' do + let(:regexp) { '()' } + let(:text) { 'foo' } + + it 'returns an array of nil matches in an array' do + is_expected.to eq([[nil], [nil], [nil], [nil]]) + end + end + context 'no capture group' do let(:regexp) { '.+' } let(:text) { 'foo' } diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index daf097f8d51..68429d792f2 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -1,11 +1,19 @@ require 'spec_helper' describe Gitlab::UsageData do - let!(:project) { create(:empty_project) } - let!(:project2) { create(:empty_project) } - let!(:board) { create(:board, project: project) } + let(:projects) { create_list(:project, 3) } + let!(:board) { create(:board, project: projects[0]) } describe '#data' do + before do + create(:jira_service, project: projects[0]) + create(:jira_service, project: projects[1]) + create(:prometheus_service, project: projects[1]) + create(:service, project: projects[0], type: 'SlackSlashCommandsService', active: true) + create(:service, project: projects[1], type: 'SlackService', active: true) + create(:service, project: projects[2], type: 'SlackService', active: true) + end + subject { described_class.data } it "gathers usage data" do @@ -25,7 +33,7 @@ describe Gitlab::UsageData do count_data = subject[:counts] expect(count_data[:boards]).to eq(1) - expect(count_data[:projects]).to eq(2) + expect(count_data[:projects]).to eq(3) expect(count_data.keys).to match_array(%i( boards @@ -49,6 +57,9 @@ describe Gitlab::UsageData do notes projects projects_imported_from_github + projects_jira_active + projects_slack_notifications_active + projects_slack_slash_active projects_prometheus_active pages_domains protected_branches @@ -59,6 +70,16 @@ describe Gitlab::UsageData do web_hooks )) end + + it 'gathers projects data correctly' do + count_data = subject[:counts] + + expect(count_data[:projects]).to eq(3) + expect(count_data[:projects_prometheus_active]).to eq(1) + expect(count_data[:projects_jira_active]).to eq(2) + expect(count_data[:projects_slack_notifications_active]).to eq(2) + expect(count_data[:projects_slack_slash_active]).to eq(1) + end end describe '#license_usage_data' do diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index 124f66a6e0e..7b39441e76e 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -325,7 +325,7 @@ describe Gitlab::Workhorse, lib: true do subject { described_class.send_git_blob(repository, blob) } - context 'when Gitaly project_raw_show feature is enabled' do + context 'when Gitaly workhorse_raw_show feature is enabled' do it 'sets the header correctly' do key, command, params = decode_workhorse_header(subject) @@ -345,7 +345,7 @@ describe Gitlab::Workhorse, lib: true do end end - context 'when Gitaly project_raw_show feature is disabled', skip_gitaly_mock: true do + context 'when Gitaly workhorse_raw_show feature is disabled', skip_gitaly_mock: true do it 'sets the header correctly' do key, command, params = decode_workhorse_header(subject) diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index dc7a0d80752..58f1a620ab4 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -98,7 +98,7 @@ describe Ability, lib: true do user2 = build(:user, external: true) users = [user1, user2] - expect(project).to receive(:owner).twice.and_return(user1) + expect(project).to receive(:owner).at_least(:once).and_return(user1) expect(described_class.users_that_can_read_project(users, project)) .to eq([user1]) @@ -109,7 +109,7 @@ describe Ability, lib: true do user2 = build(:user, external: true) users = [user1, user2] - expect(project.team).to receive(:members).twice.and_return([user1]) + expect(project.team).to receive(:members).at_least(:once).and_return([user1]) expect(described_class.users_that_can_read_project(users, project)) .to eq([user1]) @@ -140,7 +140,7 @@ describe Ability, lib: true do user2 = build(:user, external: true) users = [user1, user2] - expect(project).to receive(:owner).twice.and_return(user1) + expect(project).to receive(:owner).at_least(:once).and_return(user1) expect(described_class.users_that_can_read_project(users, project)) .to eq([user1]) @@ -151,7 +151,7 @@ describe Ability, lib: true do user2 = build(:user, external: true) users = [user1, user2] - expect(project.team).to receive(:members).twice.and_return([user1]) + expect(project.team).to receive(:members).at_least(:once).and_return([user1]) expect(described_class.users_that_can_read_project(users, project)) .to eq([user1]) diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb index e2a29e0ae70..1ad811736af 100644 --- a/spec/models/concerns/mentionable_spec.rb +++ b/spec/models/concerns/mentionable_spec.rb @@ -174,25 +174,25 @@ describe Commit, 'Mentionable' do it "is false when message doesn't reference anything" do allow(commit.raw).to receive(:message).and_return "WIP: Do something" - expect(commit.matches_cross_reference_regex?).to be false + expect(commit.matches_cross_reference_regex?).to be_falsey end it 'is true if issue #number mentioned in title' do allow(commit.raw).to receive(:message).and_return "#1" - expect(commit.matches_cross_reference_regex?).to be true + expect(commit.matches_cross_reference_regex?).to be_truthy end it 'is true if references an MR' do allow(commit.raw).to receive(:message).and_return "See merge request !12" - expect(commit.matches_cross_reference_regex?).to be true + expect(commit.matches_cross_reference_regex?).to be_truthy end it 'is true if references a commit' do allow(commit.raw).to receive(:message).and_return "a1b2c3d4" - expect(commit.matches_cross_reference_regex?).to be true + expect(commit.matches_cross_reference_regex?).to be_truthy end it 'is true if issue referenced by url' do @@ -200,7 +200,7 @@ describe Commit, 'Mentionable' do allow(commit.raw).to receive(:message).and_return Gitlab::UrlBuilder.build(issue) - expect(commit.matches_cross_reference_regex?).to be true + expect(commit.matches_cross_reference_regex?).to be_truthy end context 'with external issue tracker' do @@ -209,7 +209,13 @@ describe Commit, 'Mentionable' do it 'is true if external issues referenced' do allow(commit.raw).to receive(:message).and_return 'JIRA-123' - expect(commit.matches_cross_reference_regex?).to be true + expect(commit.matches_cross_reference_regex?).to be_truthy + end + + it 'is true if internal issues referenced' do + allow(commit.raw).to receive(:message).and_return '#123' + + expect(commit.matches_cross_reference_regex?).to be_truthy end end end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 770176451fe..d8e868265ed 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -236,6 +236,7 @@ describe Group, models: true do describe '#has_owner?' do before do @members = setup_group_members(group) + create(:group_member, :invited, :owner, group: group) end it { expect(group.has_owner?(@members[:owner])).to be_truthy } @@ -244,11 +245,13 @@ describe Group, models: true do it { expect(group.has_owner?(@members[:reporter])).to be_falsey } it { expect(group.has_owner?(@members[:guest])).to be_falsey } it { expect(group.has_owner?(@members[:requester])).to be_falsey } + it { expect(group.has_owner?(nil)).to be_falsey } end describe '#has_master?' do before do @members = setup_group_members(group) + create(:group_member, :invited, :master, group: group) end it { expect(group.has_master?(@members[:owner])).to be_falsey } @@ -257,6 +260,7 @@ describe Group, models: true do it { expect(group.has_master?(@members[:reporter])).to be_falsey } it { expect(group.has_master?(@members[:guest])).to be_falsey } it { expect(group.has_master?(@members[:requester])).to be_falsey } + it { expect(group.has_master?(nil)).to be_falsey } end describe '#lfs_enabled?' do diff --git a/spec/models/hooks/project_hook_spec.rb b/spec/models/hooks/project_hook_spec.rb index 474ae62ccec..0af270014b5 100644 --- a/spec/models/hooks/project_hook_spec.rb +++ b/spec/models/hooks/project_hook_spec.rb @@ -1,10 +1,14 @@ require 'spec_helper' describe ProjectHook, models: true do - describe "Associations" do + describe 'associations' do it { is_expected.to belong_to :project } end + describe 'validations' do + it { is_expected.to validate_presence_of(:project) } + end + describe '.push_hooks' do it 'returns hooks for push events only' do hook = create(:project_hook, push_events: true) diff --git a/spec/models/hooks/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb index 57454d2a773..8e871a41a8c 100644 --- a/spec/models/hooks/service_hook_spec.rb +++ b/spec/models/hooks/service_hook_spec.rb @@ -5,6 +5,10 @@ describe ServiceHook, models: true do it { is_expected.to belong_to :service } end + describe 'validations' do + it { is_expected.to validate_presence_of(:service) } + end + describe 'execute' do let(:hook) { build(:service_hook) } let(:data) { { key: 'value' } } diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb index 0d2b622132e..559778257fa 100644 --- a/spec/models/hooks/system_hook_spec.rb +++ b/spec/models/hooks/system_hook_spec.rb @@ -7,8 +7,7 @@ describe SystemHook, models: true do it 'sets defined default parameters' do attrs = { push_events: false, - repository_update_events: true, - enable_ssl_verification: true + repository_update_events: true } expect(system_hook).to have_attributes(attrs) end diff --git a/spec/models/list_spec.rb b/spec/models/list_spec.rb index db2c2619968..a6cc01bea5f 100644 --- a/spec/models/list_spec.rb +++ b/spec/models/list_spec.rb @@ -13,12 +13,6 @@ describe List do it { is_expected.to validate_presence_of(:position) } it { is_expected.to validate_numericality_of(:position).only_integer.is_greater_than_or_equal_to(0) } - it 'validates uniqueness of label scoped to board_id' do - create(:list) - - expect(subject).to validate_uniqueness_of(:label_id).scoped_to(:board_id) - end - context 'when list_type is set to closed' do subject { described_class.new(list_type: :closed) } diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 1eadc28869f..6f6a8ac91b8 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -155,13 +155,53 @@ describe MergeRequest, models: true do expect { subject.cache_merge_request_closes_issues!(subject.author) }.to change(subject.merge_requests_closing_issues, :count).by(1) end - it 'does not cache issues from external trackers' do - subject.project.update_attribute(:has_external_issue_tracker, true) - issue = ExternalIssue.new('JIRA-123', subject.project) - commit = double('commit1', safe_message: "Fixes #{issue.to_reference}") - allow(subject).to receive(:commits).and_return([commit]) + context 'when both internal and external issue trackers are enabled' do + before do + subject.project.has_external_issue_tracker = true + subject.project.save! + end + + it 'does not cache issues from external trackers' do + issue = ExternalIssue.new('JIRA-123', subject.project) + commit = double('commit1', safe_message: "Fixes #{issue.to_reference}") + allow(subject).to receive(:commits).and_return([commit]) - expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to change(subject.merge_requests_closing_issues, :count) + expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to change(subject.merge_requests_closing_issues, :count) + end + + it 'caches an internal issue' do + issue = create(:issue, project: subject.project) + commit = double('commit1', safe_message: "Fixes #{issue.to_reference}") + allow(subject).to receive(:commits).and_return([commit]) + + expect { subject.cache_merge_request_closes_issues!(subject.author) } + .to change(subject.merge_requests_closing_issues, :count).by(1) + end + end + + context 'when only external issue tracker enabled' do + before do + subject.project.has_external_issue_tracker = true + subject.project.issues_enabled = false + subject.project.save! + end + + it 'does not cache issues from external trackers' do + issue = ExternalIssue.new('JIRA-123', subject.project) + commit = double('commit1', safe_message: "Fixes #{issue.to_reference}") + allow(subject).to receive(:commits).and_return([commit]) + + expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to change(subject.merge_requests_closing_issues, :count) + end + + it 'does not cache an internal issue' do + issue = create(:issue, project: subject.project) + commit = double('commit1', safe_message: "Fixes #{issue.to_reference}") + allow(subject).to receive(:commits).and_return([commit]) + + expect { subject.cache_merge_request_closes_issues!(subject.author) } + .not_to change(subject.merge_requests_closing_issues, :count) + end end end diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb index cc235ad467e..76a7b07949f 100644 --- a/spec/models/notification_setting_spec.rb +++ b/spec/models/notification_setting_spec.rb @@ -11,7 +11,14 @@ RSpec.describe NotificationSetting, type: :model do it { is_expected.to validate_presence_of(:user) } it { is_expected.to validate_presence_of(:level) } - it { is_expected.to validate_uniqueness_of(:user_id).scoped_to([:source_id, :source_type]).with_message(/already exists in source/) } + + describe 'user_id' do + before do + subject.user = create(:user) + end + + it { is_expected.to validate_uniqueness_of(:user_id).scoped_to([:source_type, :source_id]).with_message(/already exists in source/) } + end context "events" do let(:user) { create(:user) } diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb index f9d060d4e0e..d4a777a9bd9 100644 --- a/spec/models/pages_domain_spec.rb +++ b/spec/models/pages_domain_spec.rb @@ -11,7 +11,7 @@ describe PagesDomain, models: true do context 'is unique' do let(:domain) { 'my.domain.com' } - it { is_expected.to validate_uniqueness_of(:domain) } + it { is_expected.to validate_uniqueness_of(:domain).case_insensitive } end { diff --git a/spec/models/project_services/microsoft_teams_service_spec.rb b/spec/models/project_services/microsoft_teams_service_spec.rb index bd50a2d1470..fb95c4cda35 100644 --- a/spec/models/project_services/microsoft_teams_service_spec.rb +++ b/spec/models/project_services/microsoft_teams_service_spec.rb @@ -112,7 +112,7 @@ describe MicrosoftTeamsService, models: true do let(:wiki_page_sample_data) do service = WikiPages::CreateService.new(project, user, opts) wiki_page = service.execute - service.hook_data(wiki_page, 'create') + Gitlab::DataBuilder::WikiPage.build(wiki_page, user, 'create') end it "calls Microsoft Teams API" do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 90769b580cd..8d916b79b13 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -309,10 +309,14 @@ describe Project, models: true do end describe 'delegation' do - it { is_expected.to delegate_method(:add_guest).to(:team) } - it { is_expected.to delegate_method(:add_reporter).to(:team) } - it { is_expected.to delegate_method(:add_developer).to(:team) } - it { is_expected.to delegate_method(:add_master).to(:team) } + [:add_guest, :add_reporter, :add_developer, :add_master, :add_user, :add_users].each do |method| + it { is_expected.to delegate_method(method).to(:team) } + end + + it { is_expected.to delegate_method(:empty_repo?).to(:repository) } + it { is_expected.to delegate_method(:members).to(:team).with_prefix(true) } + it { is_expected.to delegate_method(:count).to(:forks).with_prefix(true) } + it { is_expected.to delegate_method(:name).to(:owner).with_prefix(true).with_arguments(allow_nil: true) } end describe '#to_reference' do @@ -529,15 +533,48 @@ describe Project, models: true do end context 'with external issues tracker' do + let!(:internal_issue) { create(:issue, project: project) } before do - allow(project).to receive(:default_issues_tracker?).and_return(false) + allow(project).to receive(:external_issue_tracker).and_return(true) end - it 'returns an ExternalIssue' do - issue = project.get_issue('FOO-1234', user) - expect(issue).to be_kind_of(ExternalIssue) - expect(issue.iid).to eq 'FOO-1234' - expect(issue.project).to eq project + context 'when internal issues are enabled' do + it 'returns interlan issue' do + issue = project.get_issue(internal_issue.iid, user) + + expect(issue).to be_kind_of(Issue) + expect(issue.iid).to eq(internal_issue.iid) + expect(issue.project).to eq(project) + end + + it 'returns an ExternalIssue when internal issue does not exists' do + issue = project.get_issue('FOO-1234', user) + + expect(issue).to be_kind_of(ExternalIssue) + expect(issue.iid).to eq('FOO-1234') + expect(issue.project).to eq(project) + end + end + + context 'when internal issues are disabled' do + before do + project.issues_enabled = false + project.save! + end + + it 'returns always an External issues' do + issue = project.get_issue(internal_issue.iid, user) + expect(issue).to be_kind_of(ExternalIssue) + expect(issue.iid).to eq(internal_issue.iid.to_s) + expect(issue.project).to eq(project) + end + + it 'returns an ExternalIssue when internal issue does not exists' do + issue = project.get_issue('FOO-1234', user) + expect(issue).to be_kind_of(ExternalIssue) + expect(issue.iid).to eq('FOO-1234') + expect(issue.project).to eq(project) + end end end end diff --git a/spec/models/redirect_route_spec.rb b/spec/models/redirect_route_spec.rb index 71827421dd7..a97af28cb8e 100644 --- a/spec/models/redirect_route_spec.rb +++ b/spec/models/redirect_route_spec.rb @@ -11,7 +11,7 @@ describe RedirectRoute, models: true do describe 'validations' do it { is_expected.to validate_presence_of(:source) } it { is_expected.to validate_presence_of(:path) } - it { is_expected.to validate_uniqueness_of(:path) } + it { is_expected.to validate_uniqueness_of(:path).case_insensitive } end describe '.matching_path_and_descendants' do diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb index 1754253e0f2..12f7611fb28 100644 --- a/spec/models/route_spec.rb +++ b/spec/models/route_spec.rb @@ -15,7 +15,7 @@ describe Route, models: true do it { is_expected.to validate_presence_of(:source) } it { is_expected.to validate_presence_of(:path) } - it { is_expected.to validate_uniqueness_of(:path) } + it { is_expected.to validate_uniqueness_of(:path).case_insensitive } end describe 'callbacks' do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index a1d6d7e6e0b..20bdb7e37da 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -114,7 +114,9 @@ describe User, models: true do end it 'validates uniqueness' do - expect(subject).to validate_uniqueness_of(:username).case_insensitive + user = build(:user) + + expect(user).to validate_uniqueness_of(:username).case_insensitive end end diff --git a/spec/policies/global_policy_spec.rb b/spec/policies/global_policy_spec.rb index bb0fa0c0e9c..c3e2b603c4b 100644 --- a/spec/policies/global_policy_spec.rb +++ b/spec/policies/global_policy_spec.rb @@ -30,5 +30,25 @@ describe GlobalPolicy, models: true do it { is_expected.to be_allowed(:read_users_list) } end end + + context "for an admin" do + let(:current_user) { create(:admin) } + + context "when the public level is restricted" do + before do + stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) + end + + it { is_expected.to be_allowed(:read_users_list) } + end + + context "when the public level is not restricted" do + before do + stub_application_setting(restricted_visibility_levels: []) + end + + it { is_expected.to be_allowed(:read_users_list) } + end + end end end diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index ca435dd0218..f244975e597 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -103,6 +103,48 @@ describe ProjectPolicy, models: true do end end + context 'issues feature' do + subject { described_class.new(owner, project) } + + context 'when the feature is disabled' do + it 'does not include the issues permissions' do + project.issues_enabled = false + project.save! + + expect_disallowed :read_issue, :create_issue, :update_issue, :admin_issue + end + end + + context 'when the feature is disabled and external tracker configured' do + it 'does not include the issues permissions' do + create(:jira_service, project: project) + + project.issues_enabled = false + project.save! + + expect_disallowed :read_issue, :create_issue, :update_issue, :admin_issue + end + end + end + + context 'when a project has pending invites, and the current user is anonymous' do + let(:group) { create(:group, :public) } + let(:project) { create(:empty_project, :public, namespace: group) } + let(:user_permissions) { [:create_project, :create_issue, :create_note, :upload_file] } + let(:anonymous_permissions) { guest_permissions - user_permissions } + + subject { described_class.new(nil, project) } + + before do + create(:group_member, :invited, group: group) + end + + it 'does not grant owner access' do + expect_allowed(*anonymous_permissions) + expect_disallowed(*user_permissions) + end + end + context 'abilities for non-public projects' do let(:project) { create(:empty_project, namespace: owner.namespace) } diff --git a/spec/requests/api/group_milestones_spec.rb b/spec/requests/api/group_milestones_spec.rb new file mode 100644 index 00000000000..9b24658771f --- /dev/null +++ b/spec/requests/api/group_milestones_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe API::GroupMilestones do + let(:user) { create(:user) } + let(:group) { create(:group, :private) } + let(:project) { create(:empty_project, namespace: group) } + let!(:group_member) { create(:group_member, group: group, user: user) } + let!(:closed_milestone) { create(:closed_milestone, group: group, title: 'version1', description: 'closed milestone') } + let!(:milestone) { create(:milestone, group: group, title: 'version2', description: 'open milestone') } + + it_behaves_like 'group and project milestones', "/groups/:id/milestones" do + let(:route) { "/groups/#{group.id}/milestones" } + end + + def setup_for_group + context_group.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + context_group.add_developer(user) + public_project.update(namespace: context_group) + context_group.reload + end +end diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 9837fedb522..ff4fc802176 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -693,6 +693,19 @@ describe API::Issues do expect(json_response['confidential']).to be_falsy end + context 'links exposure' do + it 'exposes related resources full URIs' do + get api("/projects/#{project.id}/issues/#{issue.iid}", user) + + links = json_response['_links'] + + expect(links['self']).to end_with("/api/v4/projects/#{project.id}/issues/#{issue.iid}") + expect(links['notes']).to end_with("/api/v4/projects/#{project.id}/issues/#{issue.iid}/notes") + expect(links['award_emoji']).to end_with("/api/v4/projects/#{project.id}/issues/#{issue.iid}/award_emoji") + expect(links['project']).to end_with("/api/v4/projects/#{project.id}") + end + end + it "returns a project issue by internal id" do get api("/projects/#{project.id}/issues/#{issue.iid}", user) diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 9098ae6bcda..35b6522ea98 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -794,18 +794,24 @@ describe API::MergeRequests do it 'handles external issues' do jira_project = create(:jira_project, :public, name: 'JIR_EXT1') - issue = ExternalIssue.new("#{jira_project.name}-123", jira_project) - merge_request = create(:merge_request, :simple, author: user, assignee: user, source_project: jira_project) - merge_request.update_attribute(:description, "Closes #{issue.to_reference(jira_project)}") + ext_issue = ExternalIssue.new("#{jira_project.name}-123", jira_project) + issue = create(:issue, project: jira_project) + description = "Closes #{ext_issue.to_reference(jira_project)}\ncloses #{issue.to_reference}" + merge_request = create(:merge_request, + :simple, author: user, assignee: user, source_project: jira_project, description: description) get api("/projects/#{jira_project.id}/merge_requests/#{merge_request.iid}/closes_issues", user) expect(response).to have_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect(json_response.length).to eq(2) + expect(json_response.second['title']).to eq(ext_issue.title) + expect(json_response.second['id']).to eq(ext_issue.id) + expect(json_response.second['confidential']).to be_nil expect(json_response.first['title']).to eq(issue.title) expect(json_response.first['id']).to eq(issue.id) + expect(json_response.first['confidential']).not_to be_nil end it 'returns 403 if the user has no access to the merge request' do diff --git a/spec/requests/api/project_milestones_spec.rb b/spec/requests/api/project_milestones_spec.rb new file mode 100644 index 00000000000..fe8fdbfd7e4 --- /dev/null +++ b/spec/requests/api/project_milestones_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe API::ProjectMilestones do + let(:user) { create(:user) } + let!(:project) { create(:empty_project, namespace: user.namespace ) } + let!(:closed_milestone) { create(:closed_milestone, project: project, title: 'version1', description: 'closed milestone') } + let!(:milestone) { create(:milestone, project: project, title: 'version2', description: 'open milestone') } + + before do + project.team << [user, :developer] + end + + it_behaves_like 'group and project milestones', "/projects/:id/milestones" do + let(:route) { "/projects/#{project.id}/milestones" } + end + + describe 'PUT /projects/:id/milestones/:milestone_id to test observer on close' do + it 'creates an activity event when an milestone is closed' do + expect(Event).to receive(:create) + + put api("/projects/#{project.id}/milestones/#{milestone.id}", user), + state_event: 'close' + end + end +end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 6dbde8bad31..79e7e1a95df 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -159,6 +159,31 @@ describe API::Projects do expect(json_response.first).to include 'statistics' end + context 'when external issue tracker is enabled' do + let!(:jira_service) { create(:jira_service, project: project) } + + it 'includes open_issues_count' do + get api('/projects', user) + + expect(response.status).to eq 200 + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.first.keys).to include('open_issues_count') + expect(json_response.find { |hash| hash['id'] == project.id }.keys).to include('open_issues_count') + end + + it 'does not include open_issues_count if issues are disabled' do + project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED) + + get api('/projects', user) + + expect(response.status).to eq 200 + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.find { |hash| hash['id'] == project.id }.keys).not_to include('open_issues_count') + end + end + context 'and with simple=true' do it 'returns a simplified version of all the projects' do expected_keys = %w(id http_url_to_repo web_url name name_with_namespace path path_with_namespace) @@ -790,6 +815,38 @@ describe API::Projects do expect(json_response).not_to include("import_error") end + context 'links exposure' do + it 'exposes related resources full URIs' do + get api("/projects/#{project.id}", user) + + links = json_response['_links'] + + expect(links['self']).to end_with("/api/v4/projects/#{project.id}") + expect(links['issues']).to end_with("/api/v4/projects/#{project.id}/issues") + expect(links['merge_requests']).to end_with("/api/v4/projects/#{project.id}/merge_requests") + expect(links['repo_branches']).to end_with("/api/v4/projects/#{project.id}/repository/branches") + expect(links['labels']).to end_with("/api/v4/projects/#{project.id}/labels") + expect(links['events']).to end_with("/api/v4/projects/#{project.id}/events") + expect(links['members']).to end_with("/api/v4/projects/#{project.id}/members") + end + + it 'filters related URIs when their feature is not enabled' do + project = create(:empty_project, :public, + :merge_requests_disabled, + :issues_disabled, + creator_id: user.id, + namespace: user.namespace) + + get api("/projects/#{project.id}", user) + + links = json_response['_links'] + + expect(links.has_key?('merge_requests')).to be_falsy + expect(links.has_key?('issues')).to be_falsy + expect(links['self']).to end_with("/api/v4/projects/#{project.id}") + end + end + describe 'permissions' do context 'all projects' do before do diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 877bde3b9a6..66b165b438b 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -55,17 +55,22 @@ describe API::Users do context "when public level is restricted" do before do stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) - allow_any_instance_of(API::Helpers).to receive(:authenticate!).and_return(true) end - it "renders 403" do - get api("/users") - expect(response).to have_http_status(403) + context 'when authenticate as a regular user' do + it "renders 403" do + get api("/users", user) + + expect(response).to have_gitlab_http_status(403) + end end - it "renders 404" do - get api("/users/#{user.id}") - expect(response).to have_http_status(404) + context 'when authenticate as an admin' do + it "renders 200" do + get api("/users", admin) + + expect(response).to have_gitlab_http_status(200) + end end end diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index c969d08d0dd..49e815ee16c 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -69,6 +69,72 @@ describe Ci::API::Builds do end end + context 'when an old image syntax is used' do + before do + build.update!(options: { image: 'codeclimate' }) + end + + it 'starts a build' do + register_builds info: { platform: :darwin } + + expect(response).to have_http_status(201) + expect(json_response["options"]).to eq({ "image" => "codeclimate" }) + end + end + + context 'when a new image syntax is used' do + before do + build.update!(options: { image: { name: 'codeclimate' } }) + end + + it 'starts a build' do + register_builds info: { platform: :darwin } + + expect(response).to have_http_status(201) + expect(json_response["options"]).to eq({ "image" => "codeclimate" }) + end + end + + context 'when an old service syntax is used' do + before do + build.update!(options: { services: ['mysql'] }) + end + + it 'starts a build' do + register_builds info: { platform: :darwin } + + expect(response).to have_http_status(201) + expect(json_response["options"]).to eq({ "services" => ["mysql"] }) + end + end + + context 'when a new service syntax is used' do + before do + build.update!(options: { services: [name: 'mysql'] }) + end + + it 'starts a build' do + register_builds info: { platform: :darwin } + + expect(response).to have_http_status(201) + expect(json_response["options"]).to eq({ "services" => ["mysql"] }) + end + end + + context 'when no image or service is defined' do + before do + build.update!(options: {}) + end + + it 'starts a build' do + register_builds info: { platform: :darwin } + + expect(response).to have_http_status(201) + + expect(json_response["options"]).to be_empty + end + end + context 'when there is a pending build' do it 'starts a build' do register_builds info: { platform: :darwin } diff --git a/spec/serializers/build_details_entity_spec.rb b/spec/serializers/build_details_entity_spec.rb index b92c1c28ba8..1332572fffc 100644 --- a/spec/serializers/build_details_entity_spec.rb +++ b/spec/serializers/build_details_entity_spec.rb @@ -9,47 +9,96 @@ describe BuildDetailsEntity do describe '#as_json' do let(:project) { create(:project, :repository) } - let!(:build) { create(:ci_build, :failed, project: project) } + let(:pipeline) { create(:ci_pipeline, project: project) } + let(:build) { create(:ci_build, :failed, pipeline: pipeline) } let(:request) { double('request') } - let(:entity) { described_class.new(build, request: request, current_user: user, project: project) } + + let(:entity) do + described_class.new(build, request: request, + current_user: user, + project: project) + end + subject { entity.as_json } before do allow(request).to receive(:current_user).and_return(user) end + it 'contains the needed key value pairs' do + expect(subject).to include(:coverage, :erased_at, :duration) + expect(subject).to include(:runner, :pipeline) + expect(subject).to include(:raw_path, :new_issue_path) + end + context 'when the user has access to issues and merge requests' do - let!(:merge_request) do - create(:merge_request, source_project: project, source_branch: build.ref) - end + context 'when merge request orginates from the same project' do + let(:merge_request) do + create(:merge_request, source_project: project, source_branch: build.ref) + end - before do - allow(build).to receive(:merge_request).and_return(merge_request) - end + before do + allow(build).to receive(:merge_request).and_return(merge_request) + end + + it 'contains the needed key value pairs' do + expect(subject).to include(:merge_request) + expect(subject).to include(:new_issue_path) + end - it 'contains the needed key value pairs' do - expect(subject).to include(:coverage, :erased_at, :duration) - expect(subject).to include(:runner, :pipeline) - expect(subject).to include(:raw_path, :merge_request) - expect(subject).to include(:new_issue_path) + it 'exposes correct details of the merge request' do + expect(subject[:merge_request][:iid]).to eq merge_request.iid + end + + it 'has a correct merge request path' do + expect(subject[:merge_request][:path]).to include project.full_path + end end - it 'exposes details of the merge request' do - expect(subject[:merge_request]).to include(:iid, :path) + context 'when merge request is from a fork' do + let(:fork_project) do + create(:empty_project, forked_from_project: project) + end + + let(:pipeline) { create(:ci_pipeline, project: fork_project) } + + before do + allow(build).to receive(:merge_request).and_return(merge_request) + end + + let(:merge_request) do + create(:merge_request, source_project: fork_project, + target_project: project, + source_branch: build.ref) + end + + it 'contains the needed key value pairs' do + expect(subject).to include(:merge_request) + expect(subject).to include(:new_issue_path) + end + + it 'exposes details of the merge request' do + expect(subject[:merge_request][:iid]).to eq merge_request.iid + end + + it 'has a merge request path to a target project' do + expect(subject[:merge_request][:path]) + .to include project.full_path + end end - context 'when the build has been erased' do - let!(:build) { create(:ci_build, :erasable, project: project) } + context 'when the build has not been erased' do + let(:build) { create(:ci_build, :erasable, project: project) } - it 'exposes the user whom erased the build' do + it 'exposes a build erase path' do expect(subject).to include(:erase_path) end end context 'when the build has been erased' do - let!(:build) { create(:ci_build, erased_at: Time.now, project: project, erased_by: user) } + let(:build) { create(:ci_build, :erased, project: project) } - it 'exposes the user whom erased the build' do + it 'exposes the user who erased the build' do expect(subject).to include(:erased_by) end end diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index c493c08a7ae..f801506f1b6 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -488,21 +488,57 @@ describe GitPushService, services: true do end end - context "using wrong markdown" do - let(:message) { "this is some work.\n\ncloses #1" } + context "using internal issue reference" do + context 'when internal issues are disabled' do + before do + project.issues_enabled = false + project.save! + end + let(:message) { "this is some work.\n\ncloses #1" } + + it "does not initiates one api call to jira server to close the issue" do + execute_service(project, commit_author, @oldrev, @newrev, @ref ) + + expect(WebMock).not_to have_requested(:post, jira_api_transition_url('JIRA-1')) + end + + it "does not initiates one api call to jira server to comment on the issue" do + execute_service(project, commit_author, @oldrev, @newrev, @ref ) + + expect(WebMock).not_to have_requested(:post, jira_api_comment_url('JIRA-1')).with( + body: comment_body + ).once + end + end - it "does not initiates one api call to jira server to close the issue" do - execute_service(project, commit_author, @oldrev, @newrev, @ref ) + context 'when internal issues are enabled' do + let(:issue) { create(:issue, project: project) } + let(:message) { "this is some work.\n\ncloses JIRA-1 \n\n closes #{issue.to_reference}" } - expect(WebMock).not_to have_requested(:post, jira_api_transition_url('JIRA-1')) - end + it "initiates one api call to jira server to close the jira issue" do + execute_service(project, commit_author, @oldrev, @newrev, @ref ) - it "does not initiates one api call to jira server to comment on the issue" do - execute_service(project, commit_author, @oldrev, @newrev, @ref ) + expect(WebMock).to have_requested(:post, jira_api_transition_url('JIRA-1')).once + end - expect(WebMock).not_to have_requested(:post, jira_api_comment_url('JIRA-1')).with( - body: comment_body - ).once + it "initiates one api call to jira server to comment on the jira issue" do + execute_service(project, commit_author, @oldrev, @newrev, @ref ) + + expect(WebMock).to have_requested(:post, jira_api_comment_url('JIRA-1')).with( + body: comment_body + ).once + end + + it "closes the internal issue" do + execute_service(project, commit_author, @oldrev, @newrev, @ref ) + expect(issue.reload).to be_closed + end + + it "adds a note indicating that the issue is now closed" do + expect(SystemNoteService).to receive(:change_status) + .with(issue, project, commit_author, "closed", closing_commit) + execute_service(project, commit_author, @oldrev, @newrev, @ref ) + end end end end diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb index d6f4c694069..da8b60f1337 100644 --- a/spec/services/issues/close_service_spec.rb +++ b/spec/services/issues/close_service_spec.rb @@ -98,13 +98,13 @@ describe Issues::CloseService, services: true do end end - context 'external issue tracker' do + context 'internal issues disabled' do before do - allow(project).to receive(:default_issues_tracker?).and_return(false) - described_class.new(project, user).close_issue(issue) + project.issues_enabled = false + project.save! end - it 'closes the issue' do + it 'does not close the issue' do expect(issue).to be_valid expect(issue).to be_opened expect(todo.reload).to be_pending diff --git a/spec/services/issues/duplicate_service_spec.rb b/spec/services/issues/duplicate_service_spec.rb new file mode 100644 index 00000000000..82daf53b173 --- /dev/null +++ b/spec/services/issues/duplicate_service_spec.rb @@ -0,0 +1,80 @@ +require 'spec_helper' + +describe Issues::DuplicateService, services: true do + let(:user) { create(:user) } + let(:canonical_project) { create(:empty_project) } + let(:duplicate_project) { create(:empty_project) } + + let(:canonical_issue) { create(:issue, project: canonical_project) } + let(:duplicate_issue) { create(:issue, project: duplicate_project) } + + subject { described_class.new(duplicate_project, user, {}) } + + describe '#execute' do + context 'when the issues passed are the same' do + it 'does nothing' do + expect(subject).not_to receive(:close_service) + expect(SystemNoteService).not_to receive(:mark_duplicate_issue) + expect(SystemNoteService).not_to receive(:mark_canonical_issue_of_duplicate) + + subject.execute(duplicate_issue, duplicate_issue) + end + end + + context 'when the user cannot update the duplicate issue' do + before do + canonical_project.add_reporter(user) + end + + it 'does nothing' do + expect(subject).not_to receive(:close_service) + expect(SystemNoteService).not_to receive(:mark_duplicate_issue) + expect(SystemNoteService).not_to receive(:mark_canonical_issue_of_duplicate) + + subject.execute(duplicate_issue, canonical_issue) + end + end + + context 'when the user cannot comment on the canonical issue' do + before do + duplicate_project.add_reporter(user) + end + + it 'does nothing' do + expect(subject).not_to receive(:close_service) + expect(SystemNoteService).not_to receive(:mark_duplicate_issue) + expect(SystemNoteService).not_to receive(:mark_canonical_issue_of_duplicate) + + subject.execute(duplicate_issue, canonical_issue) + end + end + + context 'when the user can mark the issue as a duplicate' do + before do + canonical_project.add_reporter(user) + duplicate_project.add_reporter(user) + end + + it 'closes the duplicate issue' do + subject.execute(duplicate_issue, canonical_issue) + + expect(duplicate_issue.reload).to be_closed + expect(canonical_issue.reload).to be_open + end + + it 'adds a system note to the duplicate issue' do + expect(SystemNoteService) + .to receive(:mark_duplicate_issue).with(duplicate_issue, duplicate_project, user, canonical_issue) + + subject.execute(duplicate_issue, canonical_issue) + end + + it 'adds a system note to the canonical issue' do + expect(SystemNoteService) + .to receive(:mark_canonical_issue_of_duplicate).with(canonical_issue, canonical_project, user, duplicate_issue) + + subject.execute(duplicate_issue, canonical_issue) + end + end + end +end diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index d0b991f19ab..064be940a1c 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -491,6 +491,27 @@ describe Issues::UpdateService, services: true do include_examples 'updating mentions', Issues::UpdateService end + context 'duplicate issue' do + let(:canonical_issue) { create(:issue, project: project) } + + context 'invalid canonical_issue_id' do + it 'does not call the duplicate service' do + expect(Issues::DuplicateService).not_to receive(:new) + + update_issue(canonical_issue_id: 123456789) + end + end + + context 'valid canonical_issue_id' do + it 'calls the duplicate service with both issues' do + expect_any_instance_of(Issues::DuplicateService) + .to receive(:execute).with(issue, canonical_issue) + + update_issue(canonical_issue_id: canonical_issue.id) + end + end + end + include_examples 'issuable update service' do let(:open_issuable) { issue } let(:closed_issuable) { create(:closed_issue, project: project) } diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb index 01ef52396d7..a40d4c877bc 100644 --- a/spec/services/merge_requests/build_service_spec.rb +++ b/spec/services/merge_requests/build_service_spec.rb @@ -207,7 +207,7 @@ describe MergeRequests::BuildService, services: true do let(:source_branch) { '12345-fix-issue' } before do - allow(project).to receive(:default_issues_tracker?).and_return(false) + allow(project).to receive(:external_issue_tracker).and_return(true) end it 'sets the title to: Resolves External Issue $issue-iid' do diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index fd4011ad606..3ee834748df 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -103,7 +103,7 @@ describe Projects::UpdateService, '#execute', :services do end end - context 'when renaming project that contains container images' do + context 'when updating a project that contains container images' do before do stub_container_registry_config(enabled: true) stub_container_registry_tags(repository: /image/, tags: %w[rc1]) @@ -116,6 +116,13 @@ describe Projects::UpdateService, '#execute', :services do expect(result).to include(status: :error) expect(result[:message]).to match(/contains container registry tags/) end + + it 'allows to update other settings' do + result = update_project(project, admin, public_builds: true) + + expect(result[:status]).to eq :success + expect(project.reload.public_builds).to be true + end end context 'when passing invalid parameters' do diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index a2db3f68ff7..2a2a5c38e4b 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -261,6 +261,15 @@ describe QuickActions::InterpretService, services: true do end end + shared_examples 'duplicate command' do + it 'fetches issue and populates canonical_issue_id if content contains /duplicate issue_reference' do + issue_duplicate # populate the issue + _, updates = service.execute(content, issuable) + + expect(updates).to eq(canonical_issue_id: issue_duplicate.id) + end + end + it_behaves_like 'reopen command' do let(:content) { '/reopen' } let(:issuable) { issue } @@ -644,6 +653,41 @@ describe QuickActions::InterpretService, services: true do let(:issuable) { issue } end + context '/duplicate command' do + it_behaves_like 'duplicate command' do + let(:issue_duplicate) { create(:issue, project: project) } + let(:content) { "/duplicate #{issue_duplicate.to_reference}" } + let(:issuable) { issue } + end + + it_behaves_like 'empty command' do + let(:content) { '/duplicate' } + let(:issuable) { issue } + end + + context 'cross project references' do + it_behaves_like 'duplicate command' do + let(:other_project) { create(:empty_project, :public) } + let(:issue_duplicate) { create(:issue, project: other_project) } + let(:content) { "/duplicate #{issue_duplicate.to_reference(project)}" } + let(:issuable) { issue } + end + + it_behaves_like 'empty command' do + let(:content) { "/duplicate imaginary#1234" } + let(:issuable) { issue } + end + + it_behaves_like 'empty command' do + let(:other_project) { create(:empty_project, :private) } + let(:issue_duplicate) { create(:issue, project: other_project) } + + let(:content) { "/duplicate #{issue_duplicate.to_reference(project)}" } + let(:issuable) { issue } + end + end + end + context 'when current_user cannot :admin_issue' do let(:visitor) { create(:user) } let(:issue) { create(:issue, project: project, author: visitor) } @@ -693,6 +737,11 @@ describe QuickActions::InterpretService, services: true do let(:content) { '/remove_due_date' } let(:issuable) { issue } end + + it_behaves_like 'empty command' do + let(:content) { '/duplicate #{issue.to_reference}' } + let(:issuable) { issue } + end end context '/award command' do diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 60477b8e9ba..681b419aedf 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -1101,4 +1101,54 @@ describe SystemNoteService, services: true do expect(subject.note).to include(diffs_project_merge_request_url(project, merge_request, diff_id: diff_id, anchor: line_code)) end end + + describe '.mark_duplicate_issue' do + subject { described_class.mark_duplicate_issue(noteable, project, author, canonical_issue) } + + context 'within the same project' do + let(:canonical_issue) { create(:issue, project: project) } + + it_behaves_like 'a system note' do + let(:action) { 'duplicate' } + end + + it { expect(subject.note).to eq "marked this issue as a duplicate of #{canonical_issue.to_reference}" } + end + + context 'across different projects' do + let(:other_project) { create(:empty_project) } + let(:canonical_issue) { create(:issue, project: other_project) } + + it_behaves_like 'a system note' do + let(:action) { 'duplicate' } + end + + it { expect(subject.note).to eq "marked this issue as a duplicate of #{canonical_issue.to_reference(project)}" } + end + end + + describe '.mark_canonical_issue_of_duplicate' do + subject { described_class.mark_canonical_issue_of_duplicate(noteable, project, author, duplicate_issue) } + + context 'within the same project' do + let(:duplicate_issue) { create(:issue, project: project) } + + it_behaves_like 'a system note' do + let(:action) { 'duplicate' } + end + + it { expect(subject.note).to eq "marked #{duplicate_issue.to_reference} as a duplicate of this issue" } + end + + context 'across different projects' do + let(:other_project) { create(:empty_project) } + let(:duplicate_issue) { create(:issue, project: other_project) } + + it_behaves_like 'a system note' do + let(:action) { 'duplicate' } + end + + it { expect(subject.note).to eq "marked #{duplicate_issue.to_reference(project)} as a duplicate of this issue" } + end + end end diff --git a/spec/services/test_hook_service_spec.rb b/spec/services/test_hook_service_spec.rb deleted file mode 100644 index f99fd8434c2..00000000000 --- a/spec/services/test_hook_service_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'spec_helper' - -describe TestHookService, services: true do - let(:user) { create(:user) } - let(:project) { create(:project, :repository) } - let(:hook) { create(:project_hook, project: project) } - - describe '#execute' do - it "executes successfully" do - stub_request(:post, hook.url).to_return(status: 200) - expect(TestHookService.new.execute(hook, user)).to be_truthy - end - end -end diff --git a/spec/services/test_hooks/project_service_spec.rb b/spec/services/test_hooks/project_service_spec.rb new file mode 100644 index 00000000000..4218c15a3ce --- /dev/null +++ b/spec/services/test_hooks/project_service_spec.rb @@ -0,0 +1,188 @@ +require 'spec_helper' + +describe TestHooks::ProjectService do + let(:current_user) { create(:user) } + + describe '#execute' do + let(:project) { create(:project, :repository) } + let(:hook) { create(:project_hook, project: project) } + let(:service) { described_class.new(hook, current_user, trigger) } + let(:sample_data) { { data: 'sample' } } + let(:success_result) { { status: :success, http_status: 200, message: 'ok' } } + + context 'hook with not implemented test' do + let(:trigger) { 'not_implemented_events' } + + it 'returns error message' do + expect(hook).not_to receive(:execute) + expect(service.execute).to include({ status: :error, message: 'Testing not available for this hook' }) + end + end + + context 'push_events' do + let(:trigger) { 'push_events' } + + it 'returns error message if not enough data' do + allow(project).to receive(:empty_repo?).and_return(true) + + expect(hook).not_to receive(:execute) + expect(service.execute).to include({ status: :error, message: 'Ensure the project has at least one commit.' }) + end + + it 'executes hook' do + allow(project).to receive(:empty_repo?).and_return(false) + allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data) + + expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result) + expect(service.execute).to include(success_result) + end + end + + context 'tag_push_events' do + let(:trigger) { 'tag_push_events' } + + it 'returns error message if not enough data' do + allow(project).to receive(:empty_repo?).and_return(true) + + expect(hook).not_to receive(:execute) + expect(service.execute).to include({ status: :error, message: 'Ensure the project has at least one commit.' }) + end + + it 'executes hook' do + allow(project).to receive(:empty_repo?).and_return(false) + allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data) + + expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result) + expect(service.execute).to include(success_result) + end + end + + context 'note_events' do + let(:trigger) { 'note_events' } + + it 'returns error message if not enough data' do + expect(hook).not_to receive(:execute) + expect(service.execute).to include({ status: :error, message: 'Ensure the project has notes.' }) + end + + it 'executes hook' do + allow(project).to receive(:notes).and_return([Note.new]) + allow(Gitlab::DataBuilder::Note).to receive(:build).and_return(sample_data) + + expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result) + expect(service.execute).to include(success_result) + end + end + + context 'issues_events' do + let(:trigger) { 'issues_events' } + let(:issue) { build(:issue) } + + it 'returns error message if not enough data' do + expect(hook).not_to receive(:execute) + expect(service.execute).to include({ status: :error, message: 'Ensure the project has issues.' }) + end + + it 'executes hook' do + allow(project).to receive(:issues).and_return([issue]) + allow(issue).to receive(:to_hook_data).and_return(sample_data) + + expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result) + expect(service.execute).to include(success_result) + end + end + + context 'confidential_issues_events' do + let(:trigger) { 'confidential_issues_events' } + let(:issue) { build(:issue) } + + it 'returns error message if not enough data' do + expect(hook).not_to receive(:execute) + expect(service.execute).to include({ status: :error, message: 'Ensure the project has issues.' }) + end + + it 'executes hook' do + allow(project).to receive(:issues).and_return([issue]) + allow(issue).to receive(:to_hook_data).and_return(sample_data) + + expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result) + expect(service.execute).to include(success_result) + end + end + + context 'merge_requests_events' do + let(:trigger) { 'merge_requests_events' } + + it 'returns error message if not enough data' do + expect(hook).not_to receive(:execute) + expect(service.execute).to include({ status: :error, message: 'Ensure the project has merge requests.' }) + end + + it 'executes hook' do + create(:merge_request, source_project: project) + allow_any_instance_of(MergeRequest).to receive(:to_hook_data).and_return(sample_data) + + expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result) + expect(service.execute).to include(success_result) + end + end + + context 'job_events' do + let(:trigger) { 'job_events' } + + it 'returns error message if not enough data' do + expect(hook).not_to receive(:execute) + expect(service.execute).to include({ status: :error, message: 'Ensure the project has CI jobs.' }) + end + + it 'executes hook' do + create(:ci_build, project: project) + allow(Gitlab::DataBuilder::Build).to receive(:build).and_return(sample_data) + + expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result) + expect(service.execute).to include(success_result) + end + end + + context 'pipeline_events' do + let(:trigger) { 'pipeline_events' } + + it 'returns error message if not enough data' do + expect(hook).not_to receive(:execute) + expect(service.execute).to include({ status: :error, message: 'Ensure the project has CI pipelines.' }) + end + + it 'executes hook' do + create(:ci_empty_pipeline, project: project) + allow(Gitlab::DataBuilder::Pipeline).to receive(:build).and_return(sample_data) + + expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result) + expect(service.execute).to include(success_result) + end + end + + context 'wiki_page_events' do + let(:trigger) { 'wiki_page_events' } + + it 'returns error message if wiki disabled' do + allow(project).to receive(:wiki_enabled?).and_return(false) + + expect(hook).not_to receive(:execute) + expect(service.execute).to include({ status: :error, message: 'Ensure the wiki is enabled and has pages.' }) + end + + it 'returns error message if not enough data' do + expect(hook).not_to receive(:execute) + expect(service.execute).to include({ status: :error, message: 'Ensure the wiki is enabled and has pages.' }) + end + + it 'executes hook' do + create(:wiki_page, wiki: project.wiki) + allow(Gitlab::DataBuilder::WikiPage).to receive(:build).and_return(sample_data) + + expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result) + expect(service.execute).to include(success_result) + end + end + end +end diff --git a/spec/services/test_hooks/system_service_spec.rb b/spec/services/test_hooks/system_service_spec.rb new file mode 100644 index 00000000000..00d89924766 --- /dev/null +++ b/spec/services/test_hooks/system_service_spec.rb @@ -0,0 +1,82 @@ +require 'spec_helper' + +describe TestHooks::SystemService do + let(:current_user) { create(:user) } + + describe '#execute' do + let(:project) { create(:project, :repository) } + let(:hook) { create(:system_hook) } + let(:service) { described_class.new(hook, current_user, trigger) } + let(:sample_data) { { data: 'sample' }} + let(:success_result) { { status: :success, http_status: 200, message: 'ok' } } + + before do + allow(Project).to receive(:first).and_return(project) + end + + context 'hook with not implemented test' do + let(:trigger) { 'not_implemented_events' } + + it 'returns error message' do + expect(hook).not_to receive(:execute) + expect(service.execute).to include({ status: :error, message: 'Testing not available for this hook' }) + end + end + + context 'push_events' do + let(:trigger) { 'push_events' } + + it 'returns error message if not enough data' do + allow(project).to receive(:empty_repo?).and_return(true) + + expect(hook).not_to receive(:execute) + expect(service.execute).to include({ status: :error, message: "Ensure project \"#{project.human_name}\" has commits." }) + end + + it 'executes hook' do + allow(project).to receive(:empty_repo?).and_return(false) + allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data) + + expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result) + expect(service.execute).to include(success_result) + end + end + + context 'tag_push_events' do + let(:trigger) { 'tag_push_events' } + + it 'returns error message if not enough data' do + allow(project.repository).to receive(:tags).and_return([]) + + expect(hook).not_to receive(:execute) + expect(service.execute).to include({ status: :error, message: "Ensure project \"#{project.human_name}\" has tags." }) + end + + it 'executes hook' do + allow(project.repository).to receive(:tags).and_return(['tag']) + allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data) + + expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result) + expect(service.execute).to include(success_result) + end + end + + context 'repository_update_events' do + let(:trigger) { 'repository_update_events' } + + it 'returns error message if not enough data' do + allow(project).to receive(:commit).and_return(nil) + expect(hook).not_to receive(:execute) + expect(service.execute).to include({ status: :error, message: "Ensure project \"#{project.human_name}\" has commits." }) + end + + it 'executes hook' do + allow(project).to receive(:empty_repo?).and_return(false) + allow(Gitlab::DataBuilder::Repository).to receive(:update).and_return(sample_data) + + expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result) + expect(service.execute).to include(success_result) + end + end + end +end diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb index b5abc46e80c..7ff37c22963 100644 --- a/spec/services/web_hook_service_spec.rb +++ b/spec/services/web_hook_service_spec.rb @@ -58,7 +58,7 @@ describe WebHookService, services: true do exception = exception_class.new('Exception message') WebMock.stub_request(:post, project_hook.url).to_raise(exception) - expect(service_instance.execute).to eq([nil, exception.message]) + expect(service_instance.execute).to eq({ status: :error, message: exception.message }) expect { service_instance.execute }.not_to raise_error end end @@ -66,13 +66,13 @@ describe WebHookService, services: true do it 'handles 200 status code' do WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: 'Success') - expect(service_instance.execute).to eq([200, 'Success']) + expect(service_instance.execute).to include({ status: :success, http_status: 200, message: 'Success' }) end it 'handles 2xx status codes' do WebMock.stub_request(:post, project_hook.url).to_return(status: 201, body: 'Success') - expect(service_instance.execute).to eq([201, 'Success']) + expect(service_instance.execute).to include({ status: :success, http_status: 201, message: 'Success' }) end context 'execution logging' do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 5d5715b10ff..e7329210896 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -148,3 +148,10 @@ FactoryGirl::SyntaxRunner.class_eval do end ActiveRecord::Migration.maintain_test_schema! + +Shoulda::Matchers.configure do |config| + config.integrate do |with| + with.test_framework :rspec + with.library :rails + end +end diff --git a/spec/requests/api/milestones_spec.rb b/spec/support/api/milestones_shared_examples.rb index ab5ea3e8f2c..480e7d5151f 100644 --- a/spec/requests/api/milestones_spec.rb +++ b/spec/support/api/milestones_shared_examples.rb @@ -1,21 +1,14 @@ -require 'spec_helper' - -describe API::Milestones do - let(:user) { create(:user) } - let!(:project) { create(:empty_project, namespace: user.namespace ) } - let!(:closed_milestone) { create(:closed_milestone, project: project, title: 'version1', description: 'closed milestone') } - let!(:milestone) { create(:milestone, project: project, title: 'version2', description: 'open milestone') } +shared_examples_for 'group and project milestones' do |route_definition| + let(:resource_route) { "#{route}/#{milestone.id}" } let(:label_1) { create(:label, title: 'label_1', project: project, priority: 1) } let(:label_2) { create(:label, title: 'label_2', project: project, priority: 2) } let(:label_3) { create(:label, title: 'label_3', project: project) } + let(:merge_request) { create(:merge_request, source_project: project) } + let(:another_merge_request) { create(:merge_request, :simple, source_project: project) } - before do - project.team << [user, :developer] - end - - describe 'GET /projects/:id/milestones' do - it 'returns project milestones' do - get api("/projects/#{project.id}/milestones", user) + describe "GET #{route_definition}" do + it 'returns milestones list' do + get api(route, user) expect(response).to have_http_status(200) expect(response).to include_pagination_headers @@ -24,13 +17,13 @@ describe API::Milestones do end it 'returns a 401 error if user not authenticated' do - get api("/projects/#{project.id}/milestones") + get api(route) expect(response).to have_http_status(401) end it 'returns an array of active milestones' do - get api("/projects/#{project.id}/milestones?state=active", user) + get api("#{route}/?state=active", user) expect(response).to have_http_status(200) expect(response).to include_pagination_headers @@ -40,7 +33,7 @@ describe API::Milestones do end it 'returns an array of closed milestones' do - get api("/projects/#{project.id}/milestones?state=closed", user) + get api("#{route}/?state=closed", user) expect(response).to have_http_status(200) expect(response).to include_pagination_headers @@ -50,9 +43,9 @@ describe API::Milestones do end it 'returns an array of milestones specified by iids' do - other_milestone = create(:milestone, project: project) + other_milestone = create(:milestone, project: try(:project), group: try(:group)) - get api("/projects/#{project.id}/milestones", user), iids: [closed_milestone.iid, other_milestone.iid] + get api(route, user), iids: [closed_milestone.iid, other_milestone.iid] expect(response).to have_http_status(200) expect(json_response).to be_an Array @@ -61,25 +54,15 @@ describe API::Milestones do end it 'does not return any milestone if none found' do - get api("/projects/#{project.id}/milestones", user), iids: [Milestone.maximum(:iid).succ] + get api(route, user), iids: [Milestone.maximum(:iid).succ] expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end - end - - describe 'GET /projects/:id/milestones/:milestone_id' do - it 'returns a project milestone by id' do - get api("/projects/#{project.id}/milestones/#{milestone.id}", user) - - expect(response).to have_http_status(200) - expect(json_response['title']).to eq(milestone.title) - expect(json_response['iid']).to eq(milestone.iid) - end - it 'returns a project milestone by iids array' do - get api("/projects/#{project.id}/milestones?iids=#{closed_milestone.iid}", user) + it 'returns a milestone by iids array' do + get api("#{route}?iids=#{closed_milestone.iid}", user) expect(response.status).to eq 200 expect(response).to include_pagination_headers @@ -89,8 +72,8 @@ describe API::Milestones do expect(json_response.first['id']).to eq closed_milestone.id end - it 'returns a project milestone by searching for title' do - get api("/projects/#{project.id}/milestones", user), search: 'version2' + it 'returns a milestone by searching for title' do + get api(route, user), search: 'version2' expect(response).to have_http_status(200) expect(response).to include_pagination_headers @@ -99,8 +82,8 @@ describe API::Milestones do expect(json_response.first['id']).to eq milestone.id end - it 'returns a project milestones by searching for description' do - get api("/projects/#{project.id}/milestones", user), search: 'open' + it 'returns a milestones by searching for description' do + get api(route, user), search: 'open' expect(response).to have_http_status(200) expect(response).to include_pagination_headers @@ -110,9 +93,17 @@ describe API::Milestones do end end - describe 'GET /projects/:id/milestones/:milestone_id' do - it 'returns a project milestone by id' do - get api("/projects/#{project.id}/milestones/#{milestone.id}", user) + describe "GET #{route_definition}/:milestone_id" do + it 'returns a milestone by id' do + get api(resource_route, user) + + expect(response).to have_http_status(200) + expect(json_response['title']).to eq(milestone.title) + expect(json_response['iid']).to eq(milestone.iid) + end + + it 'returns a milestone by id' do + get api(resource_route, user) expect(response).to have_http_status(200) expect(json_response['title']).to eq(milestone.title) @@ -120,29 +111,29 @@ describe API::Milestones do end it 'returns 401 error if user not authenticated' do - get api("/projects/#{project.id}/milestones/#{milestone.id}") + get api(resource_route) expect(response).to have_http_status(401) end it 'returns a 404 error if milestone id not found' do - get api("/projects/#{project.id}/milestones/1234", user) + get api("#{route}/1234", user) expect(response).to have_http_status(404) end end - describe 'POST /projects/:id/milestones' do - it 'creates a new project milestone' do - post api("/projects/#{project.id}/milestones", user), title: 'new milestone' + describe "POST #{route_definition}" do + it 'creates a new milestone' do + post api(route, user), title: 'new milestone' expect(response).to have_http_status(201) expect(json_response['title']).to eq('new milestone') expect(json_response['description']).to be_nil end - it 'creates a new project milestone with description and dates' do - post api("/projects/#{project.id}/milestones", user), + it 'creates a new milestone with description and dates' do + post api(route, user), title: 'new milestone', description: 'release', due_date: '2013-03-02', start_date: '2013-02-02' expect(response).to have_http_status(201) @@ -152,20 +143,20 @@ describe API::Milestones do end it 'returns a 400 error if title is missing' do - post api("/projects/#{project.id}/milestones", user) + post api(route, user) expect(response).to have_http_status(400) end it 'returns a 400 error if params are invalid (duplicate title)' do - post api("/projects/#{project.id}/milestones", user), + post api(route, user), title: milestone.title, description: 'release', due_date: '2013-03-02' expect(response).to have_http_status(400) end - it 'creates a new project with reserved html characters' do - post api("/projects/#{project.id}/milestones", user), title: 'foo & bar 1.1 -> 2.2' + it 'creates a new milestone with reserved html characters' do + post api(route, user), title: 'foo & bar 1.1 -> 2.2' expect(response).to have_http_status(201) expect(json_response['title']).to eq('foo & bar 1.1 -> 2.2') @@ -173,9 +164,9 @@ describe API::Milestones do end end - describe 'PUT /projects/:id/milestones/:milestone_id' do - it 'updates a project milestone' do - put api("/projects/#{project.id}/milestones/#{milestone.id}", user), + describe "PUT #{route_definition}/:milestone_id" do + it 'updates a milestone' do + put api(resource_route, user), title: 'updated title' expect(response).to have_http_status(200) @@ -185,23 +176,21 @@ describe API::Milestones do it 'removes a due date if nil is passed' do milestone.update!(due_date: "2016-08-05") - put api("/projects/#{project.id}/milestones/#{milestone.id}", user), due_date: nil + put api(resource_route, user), due_date: nil expect(response).to have_http_status(200) expect(json_response['due_date']).to be_nil end it 'returns a 404 error if milestone id not found' do - put api("/projects/#{project.id}/milestones/1234", user), + put api("#{route}/1234", user), title: 'updated title' expect(response).to have_http_status(404) end - end - describe 'PUT /projects/:id/milestones/:milestone_id to close milestone' do - it 'updates a project milestone' do - put api("/projects/#{project.id}/milestones/#{milestone.id}", user), + it 'closes milestone' do + put api(resource_route, user), state_event: 'close' expect(response).to have_http_status(200) @@ -209,21 +198,14 @@ describe API::Milestones do end end - describe 'PUT /projects/:id/milestones/:milestone_id to test observer on close' do - it 'creates an activity event when an milestone is closed' do - expect(Event).to receive(:create) - - put api("/projects/#{project.id}/milestones/#{milestone.id}", user), - state_event: 'close' - end - end + describe "GET #{route_definition}/:milestone_id/issues" do + let(:issues_route) { "#{route}/#{milestone.id}/issues" } - describe 'GET /projects/:id/milestones/:milestone_id/issues' do before do milestone.issues << create(:issue, project: project) end - it 'returns project issues for a particular milestone' do - get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user) + it 'returns issues for a particular milestone' do + get api(issues_route, user) expect(response).to have_http_status(200) expect(response).to include_pagination_headers @@ -231,12 +213,12 @@ describe API::Milestones do expect(json_response.first['milestone']['title']).to eq(milestone.title) end - it 'returns project issues sorted by label priority' do + it 'returns issues sorted by label priority' do issue_1 = create(:labeled_issue, project: project, milestone: milestone, labels: [label_3]) issue_2 = create(:labeled_issue, project: project, milestone: milestone, labels: [label_1]) issue_3 = create(:labeled_issue, project: project, milestone: milestone, labels: [label_2]) - get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user) + get api(issues_route, user) expect(json_response.first['id']).to eq(issue_2.id) expect(json_response.second['id']).to eq(issue_3.id) @@ -244,44 +226,58 @@ describe API::Milestones do end it 'matches V4 response schema for a list of issues' do - get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user) + get api(issues_route, user) expect(response).to have_http_status(200) expect(response).to match_response_schema('public_api/v4/issues') end it 'returns a 401 error if user not authenticated' do - get api("/projects/#{project.id}/milestones/#{milestone.id}/issues") + get api(issues_route) expect(response).to have_http_status(401) end describe 'confidential issues' do - let(:public_project) { create(:empty_project, :public) } - let(:milestone) { create(:milestone, project: public_project) } - let(:issue) { create(:issue, project: public_project) } - let(:confidential_issue) { create(:issue, confidential: true, project: public_project) } + let!(:public_project) { create(:empty_project, :public) } + let!(:context_group) { try(:group) } + let!(:milestone) do + context_group ? create(:milestone, group: context_group) : create(:milestone, project: public_project) + end + let!(:issue) { create(:issue, project: public_project) } + let!(:confidential_issue) { create(:issue, confidential: true, project: public_project) } + let!(:issues_route) do + if context_group + "#{route}/#{milestone.id}/issues" + else + "/projects/#{public_project.id}/milestones/#{milestone.id}/issues" + end + end before do + # Add public project to the group in context + setup_for_group if context_group + public_project.team << [user, :developer] milestone.issues << issue << confidential_issue end it 'returns confidential issues to team members' do - get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", user) + get api(issues_route, user) expect(response).to have_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array - expect(json_response.size).to eq(2) + # 2 for projects, 3 for group(which has another project with an issue) + expect(json_response.size).to be_between(2, 3) expect(json_response.map { |issue| issue['id'] }).to include(issue.id, confidential_issue.id) end it 'does not return confidential issues to team members with guest role' do member = create(:user) - project.team << [member, :guest] + public_project.team << [member, :guest] - get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", member) + get api(issues_route, member) expect(response).to have_http_status(200) expect(response).to include_pagination_headers @@ -291,7 +287,7 @@ describe API::Milestones do end it 'does not return confidential issues to regular users' do - get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", create(:user)) + get api(issues_route, create(:user)) expect(response).to have_http_status(200) expect(response).to include_pagination_headers @@ -304,30 +300,30 @@ describe API::Milestones do issue.labels << label_2 confidential_issue.labels << label_1 - get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", user) + get api(issues_route, user) expect(response).to have_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array - expect(json_response.size).to eq(2) + # 2 for projects, 3 for group(which has another project with an issue) + expect(json_response.size).to be_between(2, 3) expect(json_response.first['id']).to eq(confidential_issue.id) expect(json_response.second['id']).to eq(issue.id) end end end - describe 'GET /projects/:id/milestones/:milestone_id/merge_requests' do - let(:merge_request) { create(:merge_request, source_project: project) } - let(:another_merge_request) { create(:merge_request, :simple, source_project: project) } + describe "GET #{route_definition}/:milestone_id/merge_requests" do + let(:merge_requests_route) { "#{route}/#{milestone.id}/merge_requests" } before do milestone.merge_requests << merge_request end - it 'returns project merge_requests for a particular milestone' do + it 'returns merge_requests for a particular milestone' do # eager-load another_merge_request another_merge_request - get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests", user) + get api(merge_requests_route, user) expect(response).to have_http_status(200) expect(json_response).to be_an Array @@ -336,12 +332,12 @@ describe API::Milestones do expect(json_response.first['milestone']['title']).to eq(milestone.title) end - it 'returns project merge_requests sorted by label priority' do + it 'returns merge_requests sorted by label priority' do merge_request_1 = create(:labeled_merge_request, source_branch: 'branch_1', source_project: project, milestone: milestone, labels: [label_2]) merge_request_2 = create(:labeled_merge_request, source_branch: 'branch_2', source_project: project, milestone: milestone, labels: [label_1]) merge_request_3 = create(:labeled_merge_request, source_branch: 'branch_3', source_project: project, milestone: milestone, labels: [label_3]) - get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests", user) + get api(merge_requests_route, user) expect(json_response.first['id']).to eq(merge_request_2.id) expect(json_response.second['id']).to eq(merge_request_1.id) @@ -349,20 +345,22 @@ describe API::Milestones do end it 'returns a 404 error if milestone id not found' do - get api("/projects/#{project.id}/milestones/1234/merge_requests", user) + not_found_route = "#{route}/1234/merge_requests" + + get api(not_found_route, user) expect(response).to have_http_status(404) end it 'returns a 404 if the user has no access to the milestone' do new_user = create :user - get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests", new_user) + get api(merge_requests_route, new_user) expect(response).to have_http_status(404) end it 'returns a 401 error if user not authenticated' do - get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests") + get api(merge_requests_route) expect(response).to have_http_status(401) end @@ -372,7 +370,7 @@ describe API::Milestones do another_merge_request.labels << label_1 merge_request.labels << label_2 - get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests", user) + get api(merge_requests_route, user) expect(response).to have_http_status(200) expect(response).to include_pagination_headers diff --git a/spec/support/devise_helpers.rb b/spec/support/devise_helpers.rb new file mode 100644 index 00000000000..890a2d9d287 --- /dev/null +++ b/spec/support/devise_helpers.rb @@ -0,0 +1,14 @@ +module DeviseHelpers + # explicitly tells Devise which mapping to use + # this is needed when we are testing a Devise controller bypassing the router + def set_devise_mapping(context:) + env = + if context.respond_to?(:env_config) + context.env_config + elsif context.respond_to?(:env) + context.env + end + + env['devise.mapping'] = Devise.mappings[:user] if env + end +end diff --git a/spec/support/features/issuable_slash_commands_shared_examples.rb b/spec/support/features/issuable_slash_commands_shared_examples.rb index 033e338fe61..035428a7d9b 100644 --- a/spec/support/features/issuable_slash_commands_shared_examples.rb +++ b/spec/support/features/issuable_slash_commands_shared_examples.rb @@ -5,8 +5,6 @@ shared_examples 'issuable record that supports quick actions in its description include QuickActionsHelpers let(:master) { create(:user) } - let(:assignee) { create(:user, username: 'bob') } - let(:guest) { create(:user) } let(:project) { create(:project, :public) } let!(:milestone) { create(:milestone, project: project, title: 'ASAP') } let!(:label_bug) { create(:label, project: project, title: 'bug') } @@ -15,8 +13,6 @@ shared_examples 'issuable record that supports quick actions in its description before do project.team << [master, :master] - project.team << [assignee, :developer] - project.team << [guest, :guest] sign_in(master) end @@ -57,6 +53,7 @@ shared_examples 'issuable record that supports quick actions in its description context 'with a note containing commands' do it 'creates a note without the commands and interpret the commands accordingly' do + assignee = create(:user, username: 'bob') write_note("Awesome!\n/assign @bob\n/label ~bug\n/milestone %\"ASAP\"") expect(page).to have_content 'Awesome!' @@ -77,6 +74,7 @@ shared_examples 'issuable record that supports quick actions in its description context 'with a note containing only commands' do it 'does not create a note but interpret the commands accordingly' do + assignee = create(:user, username: 'bob') write_note("/assign @bob\n/label ~bug\n/milestone %\"ASAP\"") expect(page).not_to have_content '/assign @bob' @@ -111,8 +109,12 @@ shared_examples 'issuable record that supports quick actions in its description context "when current user cannot close #{issuable_type}" do before do + guest = create(:user) + project.add_guest(guest) + sign_out(:user) sign_in(guest) + visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable) end @@ -146,8 +148,12 @@ shared_examples 'issuable record that supports quick actions in its description context "when current user cannot reopen #{issuable_type}" do before do + guest = create(:user) + project.add_guest(guest) + sign_out(:user) sign_in(guest) + visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable) end @@ -176,6 +182,9 @@ shared_examples 'issuable record that supports quick actions in its description context "when current user cannot change title of #{issuable_type}" do before do + guest = create(:user) + project.add_guest(guest) + sign_out(:user) sign_in(guest) visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable) @@ -267,6 +276,8 @@ shared_examples 'issuable record that supports quick actions in its description describe "preview of note on #{issuable_type}" do it 'removes quick actions from note and explains them' do + create(:user, username: 'bob') + visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable) page.within('.js-main-target-form') do diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb index b410a652126..c714d1b08a6 100644 --- a/spec/support/login_helpers.rb +++ b/spec/support/login_helpers.rb @@ -1,4 +1,6 @@ module LoginHelpers + include DeviseHelpers + # Internal: Log in as a specific user or a new user of a specific role # # user_or_role - User object, or a role to create (e.g., :admin, :user) @@ -62,7 +64,7 @@ module LoginHelpers visit new_user_session_path expect(page).to have_content('Sign in with') - check 'Remember Me' if remember_me + check 'remember_me' if remember_me click_link "oauth-login-#{provider}" end @@ -106,7 +108,7 @@ module LoginHelpers end def stub_omniauth_saml_config(messages) - Rails.application.env_config['devise.mapping'] = Devise.mappings[:user] + set_devise_mapping(context: Rails.application) Rails.application.routes.disable_clear_and_finalize = true Rails.application.routes.draw do post '/users/auth/saml' => 'omniauth_callbacks#saml' diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb index bbbbaf4c5e8..7afa57fb76b 100644 --- a/spec/support/matchers/markdown_matchers.rb +++ b/spec/support/matchers/markdown_matchers.rb @@ -17,7 +17,7 @@ module MarkdownMatchers image = actual.at_css('img[alt="Relative Image"]') expect(link['href']).to end_with('master/doc/README.md') - expect(image['src']).to end_with('master/app/assets/images/touch-icon-ipad.png') + expect(image['data-src']).to end_with('master/app/assets/images/touch-icon-ipad.png') end end @@ -70,7 +70,7 @@ module MarkdownMatchers # GollumTagsFilter matcher :parse_gollum_tags do def have_image(src) - have_css("img[src$='#{src}']") + have_css("img[data-src$='#{src}']") end prefix = '/namespace1/gitlabhq/wikis' diff --git a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb index 66e598e2691..d5bc12f3bc5 100644 --- a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb +++ b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb @@ -5,7 +5,7 @@ shared_examples "protected branches > access control > CE" do set_protected_branch_name('master') - within('.new_protected_branch') do + within('.js-new-protected-branch') do allowed_to_push_button = find(".js-allowed-to-push") unless allowed_to_push_button.text == access_type_name @@ -50,7 +50,7 @@ shared_examples "protected branches > access control > CE" do set_protected_branch_name('master') - within('.new_protected_branch') do + within('.js-new-protected-branch') do allowed_to_merge_button = find(".js-allowed-to-merge") unless allowed_to_merge_button.text == access_type_name diff --git a/spec/support/slack_mattermost_notifications_shared_examples.rb b/spec/support/slack_mattermost_notifications_shared_examples.rb index 044c09d5fde..6accf16bea4 100644 --- a/spec/support/slack_mattermost_notifications_shared_examples.rb +++ b/spec/support/slack_mattermost_notifications_shared_examples.rb @@ -78,7 +78,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do wiki_page_service = WikiPages::CreateService.new(project, user, opts) @wiki_page = wiki_page_service.execute - @wiki_page_sample_data = wiki_page_service.hook_data(@wiki_page, 'create') + @wiki_page_sample_data = Gitlab::DataBuilder::WikiPage.build(@wiki_page, user, 'create') end it "calls Slack/Mattermost API for push events" do diff --git a/spec/tasks/gitlab/task_helpers_spec.rb b/spec/tasks/gitlab/task_helpers_spec.rb index 91cc684d032..d34617be474 100644 --- a/spec/tasks/gitlab/task_helpers_spec.rb +++ b/spec/tasks/gitlab/task_helpers_spec.rb @@ -20,7 +20,6 @@ describe Gitlab::TaskHelpers do it 'checkout the version and reset to it' do expect(subject).to receive(:checkout_version).with(tag, clone_path) - expect(subject).to receive(:reset_to_version).with(tag, clone_path) subject.checkout_or_clone_version(version: version, repo: repo, target_dir: clone_path) end @@ -31,7 +30,6 @@ describe Gitlab::TaskHelpers do it 'checkout the version and reset to it with a branch name' do expect(subject).to receive(:checkout_version).with(branch, clone_path) - expect(subject).to receive(:reset_to_version).with(branch, clone_path) subject.checkout_or_clone_version(version: version, repo: repo, target_dir: clone_path) end @@ -70,20 +68,11 @@ describe Gitlab::TaskHelpers do describe '#checkout_version' do it 'clones the repo in the target dir' do expect(subject) - .to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} fetch --quiet]) + .to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} fetch --quiet origin #{tag}]) expect(subject) - .to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} checkout --quiet #{tag}]) + .to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} checkout -f --quiet FETCH_HEAD --]) subject.checkout_version(tag, clone_path) end end - - describe '#reset_to_version' do - it 'resets --hard to the given version' do - expect(subject) - .to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} reset --hard #{tag}]) - - subject.reset_to_version(tag, clone_path) - end - end end |