diff options
Diffstat (limited to 'spec')
99 files changed, 2267 insertions, 659 deletions
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 43f561f7a25..c290acb72aa 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -519,12 +519,14 @@ describe ApplicationController do get :index expect(response).to have_gitlab_http_status(404) + expect(response).to render_template('errors/not_found') end it 'renders a 403 when a message is passed to access denied' do get :index, params: { message: 'None shall pass' } expect(response).to have_gitlab_http_status(403) + expect(response).to render_template('errors/access_denied') end it 'renders a status passed to access denied' do diff --git a/spec/controllers/import/bitbucket_server_controller_spec.rb b/spec/controllers/import/bitbucket_server_controller_spec.rb index 73195463a50..bb282db5a41 100644 --- a/spec/controllers/import/bitbucket_server_controller_spec.rb +++ b/spec/controllers/import/bitbucket_server_controller_spec.rb @@ -78,7 +78,7 @@ describe Import::BitbucketServerController do end it "returns an error when the server can't be contacted" do - expect(client).to receive(:repo).with(project_key, repo_slug).and_raise(BitbucketServer::Client::ServerError) + expect(client).to receive(:repo).with(project_key, repo_slug).and_raise(::BitbucketServer::Connection::ConnectionError) post :create, params: { project: project_key, repository: repo_slug }, format: :json diff --git a/spec/controllers/omniauth_callbacks_controller_spec.rb b/spec/controllers/omniauth_callbacks_controller_spec.rb index 21936491ffc..59463462e5a 100644 --- a/spec/controllers/omniauth_callbacks_controller_spec.rb +++ b/spec/controllers/omniauth_callbacks_controller_spec.rb @@ -55,7 +55,7 @@ describe OmniauthCallbacksController, type: :controller do context 'when a redirect url is stored' do it 'redirects with fragment' do - post provider, nil, { user_return_to: '/fake/url' } + post provider, session: { user_return_to: '/fake/url' } expect(response).to redirect_to('/fake/url#L101') end @@ -63,7 +63,7 @@ describe OmniauthCallbacksController, type: :controller do context 'when a redirect url with a fragment is stored' do it 'redirects with the new fragment' do - post provider, nil, { user_return_to: '/fake/url#replaceme' } + post provider, session: { user_return_to: '/fake/url#replaceme' } expect(response).to redirect_to('/fake/url#L101') end diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index df21dc7bc85..5b3256bf409 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -1030,19 +1030,6 @@ describe Projects::IssuesController do let(:project) { create(:project, :public) } let(:file) { fixture_file_upload('spec/fixtures/csv_comma.csv') } - context 'feature disabled' do - it 'returns 404' do - sign_in(user) - project.add_maintainer(user) - - stub_feature_flags(issues_import_csv: false) - - import_csv - - expect(response).to have_gitlab_http_status :not_found - end - end - context 'unauthorized' do it 'returns 404 for guests' do sign_out(:user) @@ -1086,9 +1073,9 @@ describe Projects::IssuesController do end def import_csv - post :import_csv, namespace_id: project.namespace.to_param, - project_id: project.to_param, - file: file + post :import_csv, params: { namespace_id: project.namespace.to_param, + project_id: project.to_param, + file: file } end end diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index 7f65fe551e9..d8a331b3cf0 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -770,50 +770,6 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do end end - describe 'POST cancel_all' do - before do - project.add_developer(user) - sign_in(user) - end - - context 'when jobs are cancelable' do - before do - create_list(:ci_build, 2, :cancelable, pipeline: pipeline) - - post_cancel_all - end - - it 'redirects to a index page' do - expect(response).to have_gitlab_http_status(:found) - expect(response).to redirect_to(namespace_project_jobs_path) - end - - it 'transits to canceled' do - expect(Ci::Build.all).to all(be_canceled) - end - end - - context 'when jobs are not cancelable' do - before do - create_list(:ci_build, 2, :canceled, pipeline: pipeline) - - post_cancel_all - end - - it 'redirects to a index page' do - expect(response).to have_gitlab_http_status(:found) - expect(response).to redirect_to(namespace_project_jobs_path) - end - end - - def post_cancel_all - post :cancel_all, params: { - namespace_id: project.namespace, - project_id: project - } - end - end - describe 'POST erase' do let(:role) { :maintainer } diff --git a/spec/controllers/projects/pages_controller_spec.rb b/spec/controllers/projects/pages_controller_spec.rb index 382c1b5d124..4b742a5d427 100644 --- a/spec/controllers/projects/pages_controller_spec.rb +++ b/spec/controllers/projects/pages_controller_spec.rb @@ -28,10 +28,10 @@ describe Projects::PagesController do let(:group) { create(:group, :nested) } let(:project) { create(:project, namespace: group) } - it 'returns a 404 status code' do + it 'returns a 200 status code' do get :show, params: request_params - expect(response).to have_gitlab_http_status(404) + expect(response).to have_gitlab_http_status(200) end end end diff --git a/spec/controllers/projects/releases_controller_spec.rb b/spec/controllers/projects/releases_controller_spec.rb index f170a2ab613..5b9d21d3d5b 100644 --- a/spec/controllers/projects/releases_controller_spec.rb +++ b/spec/controllers/projects/releases_controller_spec.rb @@ -6,10 +6,6 @@ describe Projects::ReleasesController do let!(:project) { create(:project, :repository, :public) } let!(:user) { create(:user) } - before do - stub_feature_flags(releases_page: true) - end - describe 'GET #index' do it 'renders a 200' do get_index @@ -43,18 +39,6 @@ describe Projects::ReleasesController do expect(response.status).to eq(404) end end - - context 'when releases_page feature flag is disabled' do - before do - stub_feature_flags(releases_page: false) - end - - it 'renders a 404' do - get_index - - expect(response.status).to eq(404) - end - end end private diff --git a/spec/factories/wiki_pages.rb b/spec/factories/wiki_pages.rb index 2335b5118dd..ae257d769e8 100644 --- a/spec/factories/wiki_pages.rb +++ b/spec/factories/wiki_pages.rb @@ -5,7 +5,7 @@ FactoryBot.define do transient do attrs do { - title: 'Title', + title: 'Title.with.dot', content: 'Content for wiki page', format: 'markdown' } diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index baa2b1d8af5..08c27354bd2 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -97,7 +97,7 @@ describe 'Issue Boards', :js do expect(find('.board:nth-child(4)')).to have_selector('.board-card') end - it 'shows description tooltip on list title' do + it 'shows description tooltip on list title', :quarantine do page.within('.board:nth-child(2)') do expect(find('.board-title span.has-tooltip')[:title]).to eq('Test') end @@ -411,7 +411,7 @@ describe 'Issue Boards', :js do wait_for_empty_boards((2..4)) end - it 'filters by label with space after reload' do + it 'filters by label with space after reload', :quarantine do set_filter("label", "\"#{accepting.title}") click_filter_link(accepting.title) submit_filter @@ -477,7 +477,7 @@ describe 'Issue Boards', :js do end end - it 'filters by multiple labels' do + it 'filters by multiple labels', :quarantine do set_filter("label", testing.title) click_filter_link(testing.title) diff --git a/spec/features/dashboard/help_spec.rb b/spec/features/dashboard/help_spec.rb index 68bfbf22736..fa12cecc984 100644 --- a/spec/features/dashboard/help_spec.rb +++ b/spec/features/dashboard/help_spec.rb @@ -5,13 +5,23 @@ RSpec.describe 'Dashboard Help' do sign_in(create(:user)) end - it 'renders correctly markdown' do - visit help_page_path("administration/raketasks/maintenance") + context 'help dropdown' do + it 'shows the "What\'s new?" menu item' do + visit root_dashboard_path - expect(page).to have_content('Gather information about GitLab and the system it runs on') + expect(page.find('.header-help .dropdown-menu')).to have_text("What's new?") + end + end + + context 'documentation' do + it 'renders correctly markdown' do + visit help_page_path("administration/raketasks/maintenance") + + expect(page).to have_content('Gather information about GitLab and the system it runs on') - node = find('.documentation h2 a#user-content-check-gitlab-configuration') - expect(node[:href]).to eq '#check-gitlab-configuration' - expect(find(:xpath, "#{node.path}/..").text).to eq 'Check GitLab configuration' + node = find('.documentation h2 a#user-content-check-gitlab-configuration') + expect(node[:href]).to eq '#check-gitlab-configuration' + expect(find(:xpath, "#{node.path}/..").text).to eq 'Check GitLab configuration' + end end end diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb index 975b7944741..edca8f9df08 100644 --- a/spec/features/dashboard/projects_spec.rb +++ b/spec/features/dashboard/projects_spec.rb @@ -91,6 +91,7 @@ describe 'Dashboard Projects' do visit dashboard_projects_path expect(page).to have_content(project.name) + expect(find('.nav-links li:nth-child(1) .badge-pill')).to have_content(1) end it 'shows personal projects on personal projects tab', :js do @@ -121,6 +122,8 @@ describe 'Dashboard Projects' do expect(page).not_to have_content(project.name) expect(page).to have_content(project2.name) + expect(find('.nav-links li:nth-child(1) .badge-pill')).to have_content(1) + expect(find('.nav-links li:nth-child(2) .badge-pill')).to have_content(1) end end diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb index 96b22a0f64b..2284ee925a0 100644 --- a/spec/features/dashboard/todos/todos_spec.rb +++ b/spec/features/dashboard/todos/todos_spec.rb @@ -332,7 +332,7 @@ describe 'Dashboard Todos' do it 'links to the pipelines for the merge request' do href = pipelines_project_merge_request_path(project, todo.target) - expect(page).to have_link "merge request #{todo.target.to_reference(full: true)}", href + expect(page).to have_link "merge request #{todo.target.to_reference(full: true)}", href: href end end end diff --git a/spec/features/merge_request/user_merges_immediately_spec.rb b/spec/features/merge_request/user_merges_immediately_spec.rb index ea61f9675bc..84636ae355c 100644 --- a/spec/features/merge_request/user_merges_immediately_spec.rb +++ b/spec/features/merge_request/user_merges_immediately_spec.rb @@ -25,6 +25,8 @@ describe 'Merge requests > User merges immediately', :js do end it 'enables merge immediately' do + wait_for_requests + page.within '.mr-widget-body' do find('.dropdown-toggle').click diff --git a/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb index 0959f1b12f3..5188dc3625f 100644 --- a/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb +++ b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb @@ -51,22 +51,52 @@ describe 'Merge request < User sees mini pipeline graph', :js do first('.mini-pipeline-graph-dropdown-toggle') end - it 'expands when hovered' do + # Status icon button styles should update as described in + # https://gitlab.com/gitlab-org/gitlab-ce/issues/42769 + it 'has unique styles for default, :hover, :active, and :focus states' do find('.mini-pipeline-graph-dropdown-toggle') - before_width = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').outerWidth();") + default_background_color = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').css('background-color');") + default_foreground_color = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible svg').css('fill');") + default_box_shadow = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').css('box-shadow');") toggle.hover find('.mini-pipeline-graph-dropdown-toggle') - after_width = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').outerWidth();") + hover_background_color = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').css('background-color');") + hover_foreground_color = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible svg').css('fill');") + hover_box_shadow = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').css('box-shadow');") - expect(before_width).to be < after_width - end + page.driver.browser.action.click_and_hold(toggle.native).perform - it 'shows dropdown caret when hovered' do - toggle.hover + find('.mini-pipeline-graph-dropdown-toggle') + active_background_color = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').css('background-color');") + active_foreground_color = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible svg').css('fill');") + active_box_shadow = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').css('box-shadow');") + + page.driver.browser.action.release(toggle.native) + .move_by(100, 100) + .perform + + find('.mini-pipeline-graph-dropdown-toggle') + focus_background_color = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').css('background-color');") + focus_foreground_color = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible svg').css('fill');") + focus_box_shadow = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').css('box-shadow');") + + expect(default_background_color).not_to eq(hover_background_color) + expect(hover_background_color).not_to eq(active_background_color) + expect(default_background_color).not_to eq(active_background_color) + + expect(default_foreground_color).not_to eq(hover_foreground_color) + expect(hover_foreground_color).not_to eq(active_foreground_color) + expect(default_foreground_color).not_to eq(active_foreground_color) + + expect(focus_background_color).to eq(hover_background_color) + expect(focus_foreground_color).to eq(hover_foreground_color) - expect(toggle).to have_selector('.fa-caret-down') + expect(default_box_shadow).to eq('none') + expect(hover_box_shadow).to eq('none') + expect(active_box_shadow).not_to eq('none') + expect(focus_box_shadow).not_to eq('none') end it 'shows tooltip when hovered' do diff --git a/spec/features/projects/artifacts/user_browses_artifacts_spec.rb b/spec/features/projects/artifacts/user_browses_artifacts_spec.rb index 9ebbbaea911..5f630c9ffa4 100644 --- a/spec/features/projects/artifacts/user_browses_artifacts_spec.rb +++ b/spec/features/projects/artifacts/user_browses_artifacts_spec.rb @@ -25,8 +25,8 @@ describe "User browses artifacts" do page.within(".tree-table") do expect(page).to have_no_content("..") .and have_content("other_artifacts_0.1.2") - .and have_content("ci_artifacts.txt") - .and have_content("rails_sample.jpg") + .and have_content("ci_artifacts.txt 27 Bytes") + .and have_content("rails_sample.jpg 34.4 KB") end page.within(".build-header") do diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 60f37f4b74a..8230396a4cc 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -28,7 +28,6 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do end it "shows Pending tab jobs" do - expect(page).to have_link 'Cancel running' expect(page).to have_selector('.nav-links li.active', text: 'Pending') expect(page).to have_content job.short_sha expect(page).to have_content job.ref @@ -44,7 +43,6 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do it "shows Running tab jobs" do expect(page).to have_selector('.nav-links li.active', text: 'Running') - expect(page).to have_link 'Cancel running' expect(page).to have_content job.short_sha expect(page).to have_content job.ref expect(page).to have_content job.name @@ -60,7 +58,6 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do it "shows Finished tab jobs" do expect(page).to have_selector('.nav-links li.active', text: 'Finished') expect(page).to have_content 'No jobs to show' - expect(page).to have_link 'Cancel running' end end @@ -75,7 +72,6 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do expect(page).to have_content job.short_sha expect(page).to have_content job.ref expect(page).to have_content job.name - expect(page).not_to have_link 'Cancel running' end end @@ -94,23 +90,6 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do end end - describe "POST /:project/jobs/:id/cancel_all" do - before do - job.run! - visit project_jobs_path(project) - click_link "Cancel running" - end - - it 'shows all necessary content' do - expect(page).to have_selector('.nav-links li.active', text: 'All') - expect(page).to have_content 'canceled' - expect(page).to have_content job.short_sha - expect(page).to have_content job.ref - expect(page).to have_content job.name - expect(page).not_to have_link 'Cancel running' - end - end - describe "GET /:project/jobs/:id" do context "Job from project" do let(:job) { create(:ci_build, :success, :trace_live, pipeline: pipeline) } @@ -191,7 +170,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do href = new_project_issue_path(project, options) - page.within('.header-action-buttons') do + page.within('.build-sidebar') do expect(find('.js-new-issue')['href']).to include(href) end end diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 4706c28bb3d..3192c9ffad4 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -477,10 +477,11 @@ describe 'Pipeline', :js do end context 'when accessing failed jobs page' do - it 'fails to access the page' do - subject + it 'renders a 404 page' do + requests = inspect_requests { subject } - expect(page).to have_title('Access Denied') + expect(page).to have_title('Not Found') + expect(requests.first.status_code).to eq(404) end end end diff --git a/spec/features/projects/settings/user_tags_project_spec.rb b/spec/features/projects/settings/user_tags_project_spec.rb index 9357215ae6f..e3f06c042b9 100644 --- a/spec/features/projects/settings/user_tags_project_spec.rb +++ b/spec/features/projects/settings/user_tags_project_spec.rb @@ -9,13 +9,13 @@ describe 'Projects > Settings > User tags a project' do visit edit_project_path(project) end - it 'sets project tags' do - fill_in 'Tags', with: 'tag1, tag2' + it 'sets project topics' do + fill_in 'Topics', with: 'topic1, topic2' page.within '.general-settings' do click_button 'Save changes' end - expect(find_field('Tags').value).to eq 'tag1, tag2' + expect(find_field('Topics').value).to eq 'topic1, topic2' end end diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index b56bb272b46..eb70a3c41c1 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -99,6 +99,30 @@ describe 'Project' do end end + describe 'project topics' do + let(:project) { create(:project, :repository) } + let(:path) { project_path(project) } + + before do + sign_in(create(:admin)) + visit path + end + + it 'shows project topics' do + project.update_attribute(:tag_list, 'topic1') + visit path + expect(page).to have_css('.project-topic-list') + expect(page).to have_content('topic1') + end + + it 'shows up to 3 project tags' do + project.update_attribute(:tag_list, 'topic1, topic2, topic3, topic4') + visit path + expect(page).to have_css('.project-topic-list') + expect(page).to have_content('topic1, topic2, topic3 + 1 more') + end + end + describe 'copy clone URL to clipboard', :js do let(:project) { create(:project, :repository) } let(:path) { project_path(project) } diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index 80f7232f282..682fae06434 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -174,9 +174,13 @@ describe IssuesFinder do context 'filtering by upcoming milestone' do let(:params) { { milestone_title: Milestone::Upcoming.name } } + let!(:group) { create(:group, :public) } + let!(:group_member) { create(:group_member, group: group, user: user) } + let(:project_no_upcoming_milestones) { create(:project, :public) } let(:project_next_1_1) { create(:project, :public) } let(:project_next_8_8) { create(:project, :public) } + let(:project_in_group) { create(:project, :public, namespace: group) } let(:yesterday) { Date.today - 1.day } let(:tomorrow) { Date.today + 1.day } @@ -187,21 +191,22 @@ describe IssuesFinder do [ create(:milestone, :closed, project: project_no_upcoming_milestones), create(:milestone, project: project_next_1_1, title: '1.1', due_date: two_days_from_now), - create(:milestone, project: project_next_1_1, title: '8.8', due_date: ten_days_from_now), - create(:milestone, project: project_next_8_8, title: '1.1', due_date: yesterday), - create(:milestone, project: project_next_8_8, title: '8.8', due_date: tomorrow) + create(:milestone, project: project_next_1_1, title: '8.9', due_date: ten_days_from_now), + create(:milestone, project: project_next_8_8, title: '1.2', due_date: yesterday), + create(:milestone, project: project_next_8_8, title: '8.8', due_date: tomorrow), + create(:milestone, group: group, title: '9.9', due_date: tomorrow) ] end before do milestones.each do |milestone| - create(:issue, project: milestone.project, milestone: milestone, author: user, assignees: [user]) + create(:issue, project: milestone.project || project_in_group, milestone: milestone, author: user, assignees: [user]) end end - it 'returns issues in the upcoming milestone for each project' do - expect(issues.map { |issue| issue.milestone.title }).to contain_exactly('1.1', '8.8') - expect(issues.map { |issue| issue.milestone.due_date }).to contain_exactly(tomorrow, two_days_from_now) + it 'returns issues in the upcoming milestone for each project or group' do + expect(issues.map { |issue| issue.milestone.title }).to contain_exactly('1.1', '8.8', '9.9') + expect(issues.map { |issue| issue.milestone.due_date }).to contain_exactly(tomorrow, two_days_from_now, tomorrow) end end diff --git a/spec/fixtures/malicious.bundle b/spec/fixtures/malicious.bundle new file mode 100644 index 00000000000..7ba47932906 --- /dev/null +++ b/spec/fixtures/malicious.bundle @@ -0,0 +1 @@ +gitdir: foo.git diff --git a/spec/helpers/projects/error_tracking_helper_spec.rb b/spec/helpers/projects/error_tracking_helper_spec.rb new file mode 100644 index 00000000000..7516a636c93 --- /dev/null +++ b/spec/helpers/projects/error_tracking_helper_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Projects::ErrorTrackingHelper do + include Gitlab::Routing.url_helpers + + set(:project) { create(:project) } + + describe '#error_tracking_data' do + let(:setting_path) { project_settings_operations_path(project) } + + let(:index_path) do + project_error_tracking_index_path(project, format: :json) + end + + context 'without error_tracking_setting' do + it 'returns frontend configuration' do + expect(error_tracking_data(project)).to eq( + 'index-path' => index_path, + 'enable-error-tracking-link' => setting_path, + 'error-tracking-enabled' => 'false', + "illustration-path" => "/images/illustrations/cluster_popover.svg" + ) + end + end + + context 'with error_tracking_setting' do + let(:error_tracking_setting) do + create(:project_error_tracking_setting, project: project) + end + + context 'when enabled' do + before do + error_tracking_setting.update!(enabled: true) + end + + it 'show error tracking enabled' do + expect(error_tracking_data(project)).to include( + 'error-tracking-enabled' => 'true' + ) + end + end + + context 'when disabled' do + before do + error_tracking_setting.update!(enabled: false) + end + + it 'show error tracking not enabled' do + expect(error_tracking_data(project)).to include( + 'error-tracking-enabled' => 'false' + ) + end + end + end + end +end diff --git a/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js b/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js index 7237274eb43..53b9ac22fc0 100644 --- a/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js +++ b/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js @@ -1 +1,34 @@ // TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034 +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import CompareVersionsDropdown from '~/diffs/components/compare_versions_dropdown.vue'; +import diffsMockData from '../mock_data/merge_request_diffs'; + +describe('CompareVersionsDropdown', () => { + let wrapper; + const targetBranch = { branchName: 'tmp-wine-dev', versionIndex: -1 }; + + const factory = (options = {}) => { + const localVue = createLocalVue(); + + wrapper = shallowMount(CompareVersionsDropdown, { localVue, ...options }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + it('should render a correct base version link', () => { + factory({ + propsData: { + baseVersionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37', + otherVersions: diffsMockData.slice(1), + targetBranch, + }, + }); + + const links = wrapper.findAll('a'); + const lastLink = links.wrappers[links.length - 1]; + + expect(lastLink.attributes('href')).toEqual(wrapper.props('baseVersionPath')); + }); +}); diff --git a/spec/javascripts/diffs/components/compare_versions_spec.js b/spec/javascripts/diffs/components/compare_versions_spec.js index 75c66e9ca82..50135b0cf86 100644 --- a/spec/javascripts/diffs/components/compare_versions_spec.js +++ b/spec/javascripts/diffs/components/compare_versions_spec.js @@ -100,6 +100,12 @@ describe('CompareVersions', () => { }); }); + describe('baseVersionPath', () => { + it('should be set correctly from mergeRequestDiff', () => { + expect(vm.baseVersionPath).toEqual(vm.mergeRequestDiff.base_version_path); + }); + }); + describe('isWhitespaceVisible', () => { const originalHref = window.location.href; diff --git a/spec/javascripts/diffs/mock_data/merge_request_diffs.js b/spec/javascripts/diffs/mock_data/merge_request_diffs.js index d72ad7818dd..4bbef146336 100644 --- a/spec/javascripts/diffs/mock_data/merge_request_diffs.js +++ b/spec/javascripts/diffs/mock_data/merge_request_diffs.js @@ -1,42 +1,46 @@ export default [ { - versionIndex: 4, - createdAt: '2018-10-23T11:49:16.611Z', - commitsCount: 4, + base_version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37', + version_index: 4, + created_at: '2018-10-23T11:49:16.611Z', + commits_count: 4, latest: true, - shortCommitSha: 'de7a8f7f', - versionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37', - comparePath: + short_commit_sha: 'de7a8f7f', + version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37', + compare_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37&start_sha=de7a8f7f20c3ea2e0bef3ba01cfd41c21f6b4995', }, { - versionIndex: 3, - createdAt: '2018-10-23T11:46:40.617Z', - commitsCount: 3, + base_version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=36', + version_index: 3, + created_at: '2018-10-23T11:46:40.617Z', + commits_count: 3, latest: false, - shortCommitSha: 'e78fc18f', - versionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=36', - comparePath: + short_commit_sha: 'e78fc18f', + version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=36', + compare_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37&start_sha=e78fc18fa37acb2185c59ca94d4a964464feb50e', }, { - versionIndex: 2, - createdAt: '2018-10-04T09:57:39.648Z', - commitsCount: 2, + base_version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=35', + version_index: 2, + created_at: '2018-10-04T09:57:39.648Z', + commits_count: 2, latest: false, - shortCommitSha: '48da7e7e', - versionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=35', - comparePath: + short_commit_sha: '48da7e7e', + version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=35', + compare_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37&start_sha=48da7e7e9a99d41c852578bd9cb541ca4d864b3e', }, { - versionIndex: 1, - createdAt: '2018-09-25T20:30:39.493Z', - commitsCount: 1, + base_version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=20', + version_index: 1, + created_at: '2018-09-25T20:30:39.493Z', + commits_count: 1, latest: false, - shortCommitSha: '47bac2ed', - versionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=20', - comparePath: + short_commit_sha: '47bac2ed', + version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=20', + compare_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37&start_sha=47bac2ed972c5bee344c1cea159a22cd7f711dc0', }, ]; diff --git a/spec/javascripts/error_tracking/components/error_tracking_list_spec.js b/spec/javascripts/error_tracking/components/error_tracking_list_spec.js new file mode 100644 index 00000000000..08bbb390993 --- /dev/null +++ b/spec/javascripts/error_tracking/components/error_tracking_list_spec.js @@ -0,0 +1,100 @@ +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import Vuex from 'vuex'; +import ErrorTrackingList from '~/error_tracking/components/error_tracking_list.vue'; +import { GlButton, GlEmptyState, GlLoadingIcon, GlTable } from '@gitlab/ui'; + +const localVue = createLocalVue(); +localVue.use(Vuex); + +describe('ErrorTrackingList', () => { + let store; + let wrapper; + + function mountComponent({ errorTrackingEnabled = true } = {}) { + wrapper = shallowMount(ErrorTrackingList, { + localVue, + store, + propsData: { + indexPath: '/path', + enableErrorTrackingLink: '/link', + errorTrackingEnabled, + illustrationPath: 'illustration/path', + }, + }); + } + + beforeEach(() => { + const actions = { + getErrorList: () => {}, + }; + + const state = { + errors: [], + loading: true, + }; + + store = new Vuex.Store({ + actions, + state, + }); + }); + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + } + }); + + describe('loading', () => { + beforeEach(() => { + mountComponent(); + }); + + it('shows spinner', () => { + expect(wrapper.find(GlLoadingIcon).exists()).toBeTruthy(); + expect(wrapper.find(GlTable).exists()).toBeFalsy(); + expect(wrapper.find(GlButton).exists()).toBeFalsy(); + }); + }); + + describe('results', () => { + beforeEach(() => { + store.state.loading = false; + + mountComponent(); + }); + + it('shows table', () => { + expect(wrapper.find(GlLoadingIcon).exists()).toBeFalsy(); + expect(wrapper.find(GlTable).exists()).toBeTruthy(); + expect(wrapper.find(GlButton).exists()).toBeTruthy(); + }); + }); + + describe('no results', () => { + beforeEach(() => { + store.state.loading = false; + + mountComponent(); + }); + + it('shows empty table', () => { + expect(wrapper.find(GlLoadingIcon).exists()).toBeFalsy(); + expect(wrapper.find(GlTable).exists()).toBeTruthy(); + expect(wrapper.find(GlButton).exists()).toBeTruthy(); + }); + }); + + describe('error tracking feature disabled', () => { + beforeEach(() => { + mountComponent({ errorTrackingEnabled: false }); + }); + + it('shows empty state', () => { + expect(wrapper.find(GlEmptyState).exists()).toBeTruthy(); + expect(wrapper.find(GlLoadingIcon).exists()).toBeFalsy(); + expect(wrapper.find(GlTable).exists()).toBeFalsy(); + expect(wrapper.find(GlButton).exists()).toBeFalsy(); + }); + }); +}); diff --git a/spec/javascripts/error_tracking/store/mutation_spec.js b/spec/javascripts/error_tracking/store/mutation_spec.js new file mode 100644 index 00000000000..8117104bdbc --- /dev/null +++ b/spec/javascripts/error_tracking/store/mutation_spec.js @@ -0,0 +1,36 @@ +import mutations from '~/error_tracking/store/mutations'; +import * as types from '~/error_tracking/store/mutation_types'; + +describe('Error tracking mutations', () => { + describe('SET_ERRORS', () => { + let state; + + beforeEach(() => { + state = { errors: [] }; + }); + + it('camelizes response', () => { + const errors = [ + { + title: 'the title', + external_url: 'localhost:3456', + count: 100, + userCount: 10, + }, + ]; + + mutations[types.SET_ERRORS](state, errors); + + expect(state).toEqual({ + errors: [ + { + title: 'the title', + externalUrl: 'localhost:3456', + count: 100, + userCount: 10, + }, + ], + }); + }); + }); +}); diff --git a/spec/javascripts/jobs/components/sidebar_spec.js b/spec/javascripts/jobs/components/sidebar_spec.js index b0bc16d7c64..3a02351460c 100644 --- a/spec/javascripts/jobs/components/sidebar_spec.js +++ b/spec/javascripts/jobs/components/sidebar_spec.js @@ -28,7 +28,7 @@ describe('Sidebar details block', () => { store, }); - expect(vm.$el.querySelector('.js-retry-job')).toBeNull(); + expect(vm.$el.querySelector('.js-retry-button')).toBeNull(); }); }); @@ -70,7 +70,7 @@ describe('Sidebar details block', () => { }); it('should render link to retry job', () => { - expect(vm.$el.querySelector('.js-retry-job').getAttribute('href')).toEqual(job.retry_path); + expect(vm.$el.querySelector('.js-retry-button').getAttribute('href')).toEqual(job.retry_path); }); it('should render link to cancel job', () => { diff --git a/spec/javascripts/jobs/store/getters_spec.js b/spec/javascripts/jobs/store/getters_spec.js index 4195d9d3680..7931b2af79f 100644 --- a/spec/javascripts/jobs/store/getters_spec.js +++ b/spec/javascripts/jobs/store/getters_spec.js @@ -8,30 +8,6 @@ describe('Job Store Getters', () => { localState = state(); }); - describe('headerActions', () => { - describe('with new issue path', () => { - it('returns an array with action to create a new issue', () => { - localState.job.new_issue_path = 'issues/new'; - - expect(getters.headerActions(localState)).toEqual([ - { - label: 'New issue', - path: localState.job.new_issue_path, - cssClass: - 'js-new-issue btn btn-success btn-inverted d-none d-md-block d-lg-block d-xl-block', - type: 'link', - }, - ]); - }); - }); - - describe('without new issue path', () => { - it('returns an empty array', () => { - expect(getters.headerActions(localState)).toEqual([]); - }); - }); - }); - describe('headerTime', () => { describe('when the job has started key', () => { it('returns started key', () => { diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js index f320f232687..0dc7e93539a 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js +++ b/spec/javascripts/lib/utils/common_utils_spec.js @@ -347,20 +347,31 @@ describe('common_utils', () => { }); describe('parseBoolean', () => { + const { parseBoolean } = commonUtils; + it('returns true for "true"', () => { - expect(commonUtils.parseBoolean('true')).toEqual(true); + expect(parseBoolean('true')).toEqual(true); }); it('returns false for "false"', () => { - expect(commonUtils.parseBoolean('false')).toEqual(false); + expect(parseBoolean('false')).toEqual(false); }); it('returns false for "something"', () => { - expect(commonUtils.parseBoolean('something')).toEqual(false); + expect(parseBoolean('something')).toEqual(false); }); it('returns false for null', () => { - expect(commonUtils.parseBoolean(null)).toEqual(false); + expect(parseBoolean(null)).toEqual(false); + }); + + it('is idempotent', () => { + const input = ['true', 'false', 'something', null]; + input.forEach(value => { + const result = parseBoolean(value); + + expect(parseBoolean(result)).toBe(result); + }); }); }); diff --git a/spec/javascripts/notebook/cells/output/html_spec.js b/spec/javascripts/notebook/cells/output/html_spec.js index bea62f54634..3ee404fb187 100644 --- a/spec/javascripts/notebook/cells/output/html_spec.js +++ b/spec/javascripts/notebook/cells/output/html_spec.js @@ -9,6 +9,8 @@ describe('html output cell', () => { return new Component({ propsData: { rawCode, + count: 0, + index: 0, }, }).$mount(); } diff --git a/spec/javascripts/notebook/cells/output/index_spec.js b/spec/javascripts/notebook/cells/output/index_spec.js index feab7ad4212..005569f1c2d 100644 --- a/spec/javascripts/notebook/cells/output/index_spec.js +++ b/spec/javascripts/notebook/cells/output/index_spec.js @@ -10,7 +10,7 @@ describe('Output component', () => { const createComponent = output => { vm = new Component({ propsData: { - output, + outputs: [].concat(output), count: 1, }, }); @@ -51,28 +51,21 @@ describe('Output component', () => { it('renders as an image', () => { expect(vm.$el.querySelector('img')).not.toBeNull(); }); - - it('does not render the prompt', () => { - expect(vm.$el.querySelector('.prompt span')).toBeNull(); - }); }); describe('html output', () => { - beforeEach(done => { + it('renders raw HTML', () => { createComponent(json.cells[4].outputs[0]); - setTimeout(() => { - done(); - }); - }); - - it('renders raw HTML', () => { expect(vm.$el.querySelector('p')).not.toBeNull(); - expect(vm.$el.textContent.trim()).toBe('test'); + expect(vm.$el.querySelectorAll('p').length).toBe(1); + expect(vm.$el.textContent.trim()).toContain('test'); }); - it('does not render the prompt', () => { - expect(vm.$el.querySelector('.prompt span')).toBeNull(); + it('renders multiple raw HTML outputs', () => { + createComponent([json.cells[4].outputs[0], json.cells[4].outputs[0]]); + + expect(vm.$el.querySelectorAll('p').length).toBe(2); }); }); @@ -88,10 +81,6 @@ describe('Output component', () => { it('renders as an svg', () => { expect(vm.$el.querySelector('svg')).not.toBeNull(); }); - - it('does not render the prompt', () => { - expect(vm.$el.querySelector('.prompt span')).toBeNull(); - }); }); describe('default to plain text', () => { diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js index 2119a3b927a..e387367d1a2 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js @@ -1,11 +1,12 @@ import Vue from 'vue'; import ReadyToMerge from '~/vue_merge_request_widget/components/states/ready_to_merge.vue'; +import SquashBeforeMerge from '~/vue_merge_request_widget/components/states/squash_before_merge.vue'; import eventHub from '~/vue_merge_request_widget/event_hub'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; const commitMessage = 'This is the commit message'; const commitMessageWithDescription = 'This is the commit message description'; -const createComponent = (customConfig = {}) => { - const Component = Vue.extend(ReadyToMerge); +const createTestMr = customConfig => { const mr = { isPipelineActive: false, pipeline: null, @@ -16,6 +17,7 @@ const createComponent = (customConfig = {}) => { hasCI: false, ciStatus: null, sha: '12345678', + squash: false, commitMessage, commitMessageWithDescription, shouldRemoveSourceBranch: true, @@ -24,14 +26,23 @@ const createComponent = (customConfig = {}) => { Object.assign(mr, customConfig.mr); - const service = { - merge() {}, - poll() {}, - }; + return mr; +}; + +const createTestService = () => ({ + merge() {}, + poll() {}, +}); + +const createComponent = (customConfig = {}) => { + const Component = Vue.extend(ReadyToMerge); return new Component({ el: document.createElement('div'), - propsData: { mr, service }, + propsData: { + mr: createTestMr(customConfig), + service: createTestService(), + }, }); }; @@ -612,6 +623,47 @@ describe('ReadyToMerge', () => { }); }); + describe('Squash checkbox component', () => { + let wrapper; + const localVue = createLocalVue(); + + const createLocalComponent = (customConfig = {}) => { + wrapper = shallowMount(localVue.extend(ReadyToMerge), { + localVue, + propsData: { + mr: createTestMr(customConfig), + service: createTestService(), + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + const findCheckboxElement = () => wrapper.find(SquashBeforeMerge); + + it('should be rendered when squash before merge is enabled and there is more than 1 commit', () => { + createLocalComponent({ + mr: { commitsCount: 2, enableSquashBeforeMerge: true }, + }); + + expect(findCheckboxElement().exists()).toBeTruthy(); + }); + + it('should not be rendered when squash before merge is disabled', () => { + createLocalComponent({ mr: { commitsCount: 2, enableSquashBeforeMerge: false } }); + + expect(findCheckboxElement().exists()).toBeFalsy(); + }); + + it('should not be rendered when there is only 1 commit', () => { + createLocalComponent({ mr: { commitsCount: 1, enableSquashBeforeMerge: true } }); + + expect(findCheckboxElement().exists()).toBeFalsy(); + }); + }); + describe('Merge controls', () => { describe('when allowed to merge', () => { beforeEach(() => { diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js new file mode 100644 index 00000000000..d6d8eecfcb9 --- /dev/null +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js @@ -0,0 +1,100 @@ +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import SquashBeforeMerge from '~/vue_merge_request_widget/components/states/squash_before_merge.vue'; + +const localVue = createLocalVue(); + +describe('Squash before merge component', () => { + let wrapper; + + const createComponent = props => { + wrapper = shallowMount(localVue.extend(SquashBeforeMerge), { + localVue, + sync: false, + propsData: { + ...props, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + describe('checkbox', () => { + const findCheckbox = () => wrapper.find('.qa-squash-checkbox'); + + it('is unchecked if passed value prop is false', () => { + createComponent({ + value: false, + }); + + expect(findCheckbox().element.checked).toBeFalsy(); + }); + + it('is checked if passed value prop is true', () => { + createComponent({ + value: true, + }); + + expect(findCheckbox().element.checked).toBeTruthy(); + }); + + it('changes value on click', done => { + createComponent({ + value: false, + }); + + findCheckbox().element.checked = true; + + findCheckbox().trigger('change'); + + wrapper.vm.$nextTick(() => { + expect(findCheckbox().element.checked).toBeTruthy(); + done(); + }); + }); + + it('is disabled if isDisabled prop is true', () => { + createComponent({ + value: false, + isDisabled: true, + }); + + expect(findCheckbox().attributes('disabled')).toBeTruthy(); + }); + }); + + describe('about link', () => { + it('is not rendered if no help path is passed', () => { + createComponent({ + value: false, + }); + + const aboutLink = wrapper.find('a'); + + expect(aboutLink.exists()).toBeFalsy(); + }); + + it('is rendered if help path is passed', () => { + createComponent({ + value: false, + helpPath: 'test-path', + }); + + const aboutLink = wrapper.find('a'); + + expect(aboutLink.exists()).toBeTruthy(); + }); + + it('should have a correct help path if passed', () => { + createComponent({ + value: false, + helpPath: 'test-path', + }); + + const aboutLink = wrapper.find('a'); + + expect(aboutLink.attributes('href')).toEqual('test-path'); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js index 8187b3204b1..12ee804f668 100644 --- a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js +++ b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js @@ -31,6 +31,12 @@ describe('Suggestion Diff component', () => { expect(header.innerHTML.includes('Suggested change')).toBe(true); }); + it('renders a help button', () => { + const helpBtn = vm.$el.querySelector('.js-help-btn'); + + expect(helpBtn).not.toBeNull(); + }); + it('renders an apply button', () => { const applyBtn = vm.$el.querySelector('.qa-apply-btn'); diff --git a/spec/lib/banzai/filter/footnote_filter_spec.rb b/spec/lib/banzai/filter/footnote_filter_spec.rb new file mode 100644 index 00000000000..2e50e4e2351 --- /dev/null +++ b/spec/lib/banzai/filter/footnote_filter_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Banzai::Filter::FootnoteFilter do + include FilterSpecHelper + + # first[^1] and second[^second] + # [^1]: one + # [^second]: two + let(:footnote) do + <<~EOF + <p>first<sup><a href="#fn1" id="fnref1">1</a></sup> and second<sup><a href="#fn2" id="fnref2">2</a></sup></p> + <ol> + <li id="fn1"> + <p>one <a href="#fnref1">↩</a></p> + </li> + <li id="fn2"> + <p>two <a href="#fnref2">↩</a></p> + </li> + </ol> + EOF + end + + let(:filtered_footnote) do + <<~EOF + <p>first<sup class="footnote-ref"><a href="#fn1-#{identifier}" id="fnref1-#{identifier}">1</a></sup> and second<sup class="footnote-ref"><a href="#fn2-#{identifier}" id="fnref2-#{identifier}">2</a></sup></p> + <section class="footnotes"><ol> + <li id="fn1-#{identifier}"> + <p>one <a href="#fnref1-#{identifier}" class="footnote-backref">↩</a></p> + </li> + <li id="fn2-#{identifier}"> + <p>two <a href="#fnref2-#{identifier}" class="footnote-backref">↩</a></p> + </li> + </ol></section> + EOF + end + + context 'when footnotes exist' do + let(:doc) { filter(footnote) } + let(:link_node) { doc.css('sup > a').first } + let(:identifier) { link_node[:id].delete_prefix('fnref1-') } + + it 'properly adds the necessary ids and classes' do + expect(doc.to_html).to eq filtered_footnote + end + end +end diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb index 0b3c2390304..836af18e0b6 100644 --- a/spec/lib/banzai/filter/sanitization_filter_spec.rb +++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb @@ -246,7 +246,7 @@ describe Banzai::Filter::SanitizationFilter do 'protocol-based JS injection: spaces and entities' => { input: '<a href="  javascript:alert(\'XSS\');">foo</a>', - output: '<a href>foo</a>' + output: '<a href="">foo</a>' }, 'protocol whitespace' => { @@ -300,5 +300,48 @@ describe Banzai::Filter::SanitizationFilter do expect(act.to_html).to eq exp end + + describe 'footnotes' do + it 'allows correct footnote id property on links' do + exp = %q{<a href="#fn1" id="fnref1">foo/bar.md</a>} + act = filter(exp) + + expect(act.to_html).to eq exp + end + + it 'allows correct footnote id property on li element' do + exp = %q{<ol><li id="fn1">footnote</li></ol>} + act = filter(exp) + + expect(act.to_html).to eq exp + end + + it 'removes invalid id for footnote links' do + exp = %q{<a href="#fn1">link</a>} + + %w[fnrefx test xfnref1].each do |id| + act = filter(%Q{<a href="#fn1" id="#{id}">link</a>}) + + expect(act.to_html).to eq exp + end + end + + it 'removes invalid id for footnote li' do + exp = %q{<ol><li>footnote</li></ol>} + + %w[fnx test xfn1].each do |id| + act = filter(%Q{<ol><li id="#{id}">footnote</li></ol>}) + + expect(act.to_html).to eq exp + end + end + + it 'allows footnotes numbered higher than 9' do + exp = %q{<a href="#fn15" id="fnref15">link</a><ol><li id="fn15">footnote</li></ol>} + act = filter(exp) + + expect(act.to_html).to eq exp + end + end end end diff --git a/spec/lib/banzai/pipeline/full_pipeline_spec.rb b/spec/lib/banzai/pipeline/full_pipeline_spec.rb index e9c7a2f352e..3634655c6a5 100644 --- a/spec/lib/banzai/pipeline/full_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/full_pipeline_spec.rb @@ -25,4 +25,36 @@ describe Banzai::Pipeline::FullPipeline do expect(result).to include(%{data-original='\">bad things'}) end end + + describe 'footnotes' do + let(:project) { create(:project, :public) } + let(:html) { described_class.to_html(footnote_markdown, project: project) } + let(:identifier) { html[/.*fnref1-(\d+).*/, 1] } + let(:footnote_markdown) do + <<~EOF + first[^1] and second[^second] + [^1]: one + [^second]: two + EOF + end + + let(:filtered_footnote) do + <<~EOF + <p dir="auto">first<sup class="footnote-ref"><a href="#fn1-#{identifier}" id="fnref1-#{identifier}">1</a></sup> and second<sup class="footnote-ref"><a href="#fn2-#{identifier}" id="fnref2-#{identifier}">2</a></sup></p> + + <section class="footnotes"><ol> + <li id="fn1-#{identifier}"> + <p>one <a href="#fnref1-#{identifier}" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p> + </li> + <li id="fn2-#{identifier}"> + <p>two <a href="#fnref2-#{identifier}" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p> + </li> + </ol></section> + EOF + end + + it 'properly adds the necessary ids and classes' do + expect(html.lines.map(&:strip).join("\n")).to eq filtered_footnote + end + end end diff --git a/spec/lib/bitbucket_server/client_spec.rb b/spec/lib/bitbucket_server/client_spec.rb index 5de0a9a65b5..b021e69800a 100644 --- a/spec/lib/bitbucket_server/client_spec.rb +++ b/spec/lib/bitbucket_server/client_spec.rb @@ -17,12 +17,6 @@ describe BitbucketServer::Client do subject.pull_requests(project, repo_slug) end - - it 'throws an exception when connection fails' do - allow(BitbucketServer::Collection).to receive(:new).and_raise(OpenSSL::SSL::SSLError) - - expect { subject.pull_requests(project, repo_slug) }.to raise_error(described_class::ServerError) - end end describe '#activities' do diff --git a/spec/lib/bitbucket_server/connection_spec.rb b/spec/lib/bitbucket_server/connection_spec.rb index b5da4cb1a49..ab8a5b89608 100644 --- a/spec/lib/bitbucket_server/connection_spec.rb +++ b/spec/lib/bitbucket_server/connection_spec.rb @@ -26,6 +26,12 @@ describe BitbucketServer::Connection do expect { subject.get(url) }.to raise_error(described_class::ConnectionError) end + + it 'throws an exception upon a network error' do + WebMock.stub_request(:get, url).with(headers: { 'Accept' => 'application/json' }).to_raise(OpenSSL::SSL::SSLError) + + expect { subject.get(url) }.to raise_error(described_class::ConnectionError) + end end describe '#post' do @@ -42,6 +48,12 @@ describe BitbucketServer::Connection do expect { subject.post(url, payload) }.to raise_error(described_class::ConnectionError) end + + it 'throws an exception upon a network error' do + WebMock.stub_request(:post, url).with(headers: { 'Accept' => 'application/json' }).to_raise(OpenSSL::SSL::SSLError) + + expect { subject.post(url, payload) }.to raise_error(described_class::ConnectionError) + end end describe '#delete' do @@ -63,6 +75,12 @@ describe BitbucketServer::Connection do expect { subject.delete(:branches, branch_path, payload) }.to raise_error(described_class::ConnectionError) end + + it 'throws an exception upon a network error' do + WebMock.stub_request(:delete, branch_url).with(headers: headers).to_raise(OpenSSL::SSL::SSLError) + + expect { subject.delete(:branches, branch_path, payload) }.to raise_error(described_class::ConnectionError) + end end end end diff --git a/spec/lib/feature_spec.rb b/spec/lib/feature_spec.rb index 9d56c62ae57..630732614b2 100644 --- a/spec/lib/feature_spec.rb +++ b/spec/lib/feature_spec.rb @@ -182,4 +182,18 @@ describe Feature do expect(described_class.disabled?(:enabled_feature_flag)).to be_falsey end end + + describe Feature::Target do + describe '#targets' do + let(:project) { create(:project) } + let(:user_name) { project.owner.username } + + subject { described_class.new(user: user_name, project: project.full_path) } + + it 'returns all found targets' do + expect(subject.targets).to be_an(Array) + expect(subject.targets).to eq([project.owner, project]) + end + end + end end diff --git a/spec/lib/gitlab/access/branch_protection_spec.rb b/spec/lib/gitlab/access/branch_protection_spec.rb new file mode 100644 index 00000000000..7f2979e8e28 --- /dev/null +++ b/spec/lib/gitlab/access/branch_protection_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Access::BranchProtection do + describe '#any?' do + using RSpec::Parameterized::TableSyntax + + where(:level, :result) do + Gitlab::Access::PROTECTION_NONE | false + Gitlab::Access::PROTECTION_DEV_CAN_PUSH | true + Gitlab::Access::PROTECTION_DEV_CAN_MERGE | true + Gitlab::Access::PROTECTION_FULL | true + end + + with_them do + it { expect(described_class.new(level).any?).to eq(result) } + end + end + + describe '#developer_can_push?' do + using RSpec::Parameterized::TableSyntax + + where(:level, :result) do + Gitlab::Access::PROTECTION_NONE | false + Gitlab::Access::PROTECTION_DEV_CAN_PUSH | true + Gitlab::Access::PROTECTION_DEV_CAN_MERGE | false + Gitlab::Access::PROTECTION_FULL | false + end + + with_them do + it do + expect(described_class.new(level).developer_can_push?).to eq(result) + end + end + end + + describe '#developer_can_merge?' do + using RSpec::Parameterized::TableSyntax + + where(:level, :result) do + Gitlab::Access::PROTECTION_NONE | false + Gitlab::Access::PROTECTION_DEV_CAN_PUSH | false + Gitlab::Access::PROTECTION_DEV_CAN_MERGE | true + Gitlab::Access::PROTECTION_FULL | false + end + + with_them do + it do + expect(described_class.new(level).developer_can_merge?).to eq(result) + end + end + end +end diff --git a/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb index 53c071f0268..510a0074554 100644 --- a/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb +++ b/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' +# rubocop:disable RSpec/FactoriesInMigrationSpecs describe Gitlab::BackgroundMigration::BackfillProjectRepositories do let(:group) { create(:group, name: 'foo', path: 'foo') } @@ -34,6 +35,7 @@ describe Gitlab::BackgroundMigration::BackfillProjectRepositories do let!(:project_hashed_storage_2) { create(:project, name: 'bar', path: 'bar', namespace: group, storage_version: 2) } let!(:project_legacy_storage_3) { create(:project, name: 'baz', path: 'baz', namespace: group, storage_version: 0) } let!(:project_legacy_storage_4) { create(:project, name: 'zoo', path: 'zoo', namespace: group, storage_version: nil) } + let!(:project_legacy_storage_5) { create(:project, name: 'test', path: 'test', namespace: group, storage_version: nil) } describe '.on_hashed_storage' do it 'finds projects with repository on hashed storage' do @@ -47,7 +49,7 @@ describe Gitlab::BackgroundMigration::BackfillProjectRepositories do it 'finds projects with repository on legacy storage' do projects = described_class.on_legacy_storage.pluck(:id) - expect(projects).to match_array([project_legacy_storage_3.id, project_legacy_storage_4.id]) + expect(projects).to match_array([project_legacy_storage_3.id, project_legacy_storage_4.id, project_legacy_storage_5.id]) end end @@ -58,7 +60,7 @@ describe Gitlab::BackgroundMigration::BackfillProjectRepositories do projects = described_class.without_project_repository.pluck(:id) - expect(projects).to contain_exactly(project_hashed_storage_2.id, project_legacy_storage_4.id) + expect(projects).to contain_exactly(project_hashed_storage_2.id, project_legacy_storage_4.id, project_legacy_storage_5.id) end end @@ -78,17 +80,27 @@ describe Gitlab::BackgroundMigration::BackfillProjectRepositories do expect(project.disk_path).to eq(project_legacy_storage_3.disk_path) end + it 'returns the correct disk_path using the route entry' do + project_legacy_storage_5.route.update(path: 'zoo/new-test') + project = described_class.find(project_legacy_storage_5.id) + + expect(project.disk_path).to eq('zoo/new-test') + end + it 'raises OrphanedNamespaceError when any parent namespace does not exist' do subgroup = create(:group, parent: group) project_orphaned_namespace = create(:project, name: 'baz', path: 'baz', namespace: subgroup, storage_version: nil) subgroup.update_column(:parent_id, Namespace.maximum(:id).succ) project = described_class.find(project_orphaned_namespace.id) + project.route.destroy + subgroup.route.destroy - expect { project.disk_path } + expect { project.reload.disk_path } .to raise_error(Gitlab::BackgroundMigration::BackfillProjectRepositories::OrphanedNamespaceError) end end end end end +# rubocop:enable RSpec/FactoriesInMigrationSpecs diff --git a/spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb b/spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb index f92acf61682..f974dc8fda2 100644 --- a/spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb +++ b/spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::BackgroundMigration::CreateGpgKeySubkeysFromGpgKeys, :migration, schema: 20171005130944 do context 'when GpgKey exists' do - let!(:gpg_key) { create(:gpg_key, key: GpgHelpers::User3.public_key) } + let!(:gpg_key) { create(:gpg_key, key: GpgHelpers::User3.public_key) } # rubocop:disable RSpec/FactoriesInMigrationSpecs before do GpgKeySubkey.destroy_all # rubocop: disable DestroyAll diff --git a/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb b/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb index 1969aed51da..27281333348 100644 --- a/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb +++ b/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb @@ -1,5 +1,6 @@ require 'spec_helper' +# rubocop:disable RSpec/FactoriesInMigrationSpecs describe Gitlab::BackgroundMigration::DeleteDiffFiles, :migration, :sidekiq, schema: 20180619121030 do describe '#perform' do context 'when diff files can be deleted' do @@ -71,3 +72,4 @@ describe Gitlab::BackgroundMigration::DeleteDiffFiles, :migration, :sidekiq, sch end end end +# rubocop:enable RSpec/FactoriesInMigrationSpecs diff --git a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb index 5dce3fcbcb6..bc71a90605a 100644 --- a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb +++ b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb @@ -1,5 +1,6 @@ require 'spec_helper' +# rubocop:disable RSpec/FactoriesInMigrationSpecs describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :migration, schema: 20171114162227 do include GitHelpers @@ -324,3 +325,4 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :m end end end +# rubocop:enable RSpec/FactoriesInMigrationSpecs diff --git a/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb b/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb index 5432d270555..188969951a6 100644 --- a/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb +++ b/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb @@ -1,5 +1,6 @@ require 'spec_helper' +# rubocop:disable RSpec/FactoriesInMigrationSpecs describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads::Event, :migration, schema: 20170608152748 do describe '#commit_title' do it 'returns nil when there are no commits' do @@ -429,3 +430,4 @@ describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads, :migrati end end end +# rubocop:enable RSpec/FactoriesInMigrationSpecs diff --git a/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb b/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb index 021e1d14b18..ea8bdd48e72 100644 --- a/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb +++ b/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb @@ -1,5 +1,6 @@ require 'spec_helper' +# rubocop:disable RSpec/FactoriesInMigrationSpecs describe Gitlab::BackgroundMigration::MigrateSystemUploadsToNewFolder, :delete do let(:migration) { described_class.new } @@ -17,3 +18,4 @@ describe Gitlab::BackgroundMigration::MigrateSystemUploadsToNewFolder, :delete d end end end +# rubocop:enable RSpec/FactoriesInMigrationSpecs diff --git a/spec/lib/gitlab/background_migration/move_personal_snippet_files_spec.rb b/spec/lib/gitlab/background_migration/move_personal_snippet_files_spec.rb index 2e77e80ee46..593486fc56c 100644 --- a/spec/lib/gitlab/background_migration/move_personal_snippet_files_spec.rb +++ b/spec/lib/gitlab/background_migration/move_personal_snippet_files_spec.rb @@ -1,5 +1,6 @@ require 'spec_helper' +# rubocop:disable RSpec/FactoriesInMigrationSpecs describe Gitlab::BackgroundMigration::MovePersonalSnippetFiles do let(:test_dir) { File.join(Rails.root, 'tmp', 'tests', 'move_snippet_files_test') } let(:old_uploads_dir) { File.join('uploads', 'system', 'personal_snippet') } @@ -70,3 +71,4 @@ describe Gitlab::BackgroundMigration::MovePersonalSnippetFiles do "[an upload](#{path})" end end +# rubocop:enable RSpec/FactoriesInMigrationSpecs diff --git a/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb b/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb index 4f1b01eed41..8e3cb36d313 100644 --- a/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb +++ b/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' +# rubocop:disable RSpec/FactoriesInMigrationSpecs describe Gitlab::BackgroundMigration::PopulateClusterKubernetesNamespaceTable, :migration, schema: 20181022173835 do let(:migration) { described_class.new } let(:clusters) { create_list(:cluster, 10, :project, :provided_by_gcp) } @@ -95,3 +96,4 @@ describe Gitlab::BackgroundMigration::PopulateClusterKubernetesNamespaceTable, : end end end +# rubocop:enable RSpec/FactoriesInMigrationSpecs diff --git a/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb b/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb index 6ab126ad39a..3e009fed0f1 100644 --- a/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb +++ b/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' +# rubocop:disable RSpec/FactoriesInMigrationSpecs describe Gitlab::BackgroundMigration::PopulateExternalPipelineSource, :migration, schema: 20180916011959 do let(:migration) { described_class.new } @@ -65,3 +66,4 @@ describe Gitlab::BackgroundMigration::PopulateExternalPipelineSource, :migration it_behaves_like 'no changes' end end +# rubocop:enable RSpec/FactoriesInMigrationSpecs diff --git a/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb index e99257e3481..ff1bd9f7850 100644 --- a/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb +++ b/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb @@ -1,5 +1,6 @@ require 'rails_helper' +# rubocop:disable RSpec/FactoriesInMigrationSpecs describe Gitlab::BackgroundMigration::PopulateMergeRequestMetricsWithEventsData, :migration, schema: 20171128214150 do # commits_count attribute is added in a next migration before do @@ -128,3 +129,4 @@ describe Gitlab::BackgroundMigration::PopulateMergeRequestMetricsWithEventsData, end end end +# rubocop:enable RSpec/FactoriesInMigrationSpecs diff --git a/spec/lib/gitlab/ci/config/entry/global_spec.rb b/spec/lib/gitlab/ci/config/entry/global_spec.rb index 941ef33c8a4..7651f594a4c 100644 --- a/spec/lib/gitlab/ci/config/entry/global_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/global_spec.rb @@ -160,8 +160,7 @@ describe Gitlab::Ci::Config::Entry::Global do variables: { 'VAR' => 'value' }, ignore: false, after_script: ['make clean'], - only: { refs: %w[branches tags] }, - except: {} }, + only: { refs: %w[branches tags] } }, spinach: { name: :spinach, before_script: [], script: %w[spinach], @@ -172,8 +171,7 @@ describe Gitlab::Ci::Config::Entry::Global do variables: {}, ignore: false, after_script: ['make clean'], - only: { refs: %w[branches tags] }, - except: {} } + only: { refs: %w[branches tags] } } ) end end diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index 3d0b98eb238..0560eb42e4d 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -258,8 +258,7 @@ describe Gitlab::Ci::Config::Entry::Job do stage: 'test', ignore: false, after_script: %w[cleanup], - only: { refs: %w[branches tags] }, - except: {}) + only: { refs: %w[branches tags] }) end end end diff --git a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb index d97be76f0e0..271ee30df3c 100644 --- a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb @@ -67,14 +67,12 @@ describe Gitlab::Ci::Config::Entry::Jobs do script: %w[rspec], ignore: false, stage: 'test', - only: { refs: %w[branches tags] }, - except: {} }, + only: { refs: %w[branches tags] } }, spinach: { name: :spinach, script: %w[spinach], ignore: false, stage: 'test', - only: { refs: %w[branches tags] }, - except: {} }) + only: { refs: %w[branches tags] } }) end end diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb index 83001b7fdd8..1c987e13a9a 100644 --- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb @@ -168,8 +168,33 @@ describe Gitlab::Ci::Config::Entry::Policy do end end + describe '#value' do + context 'when default value has been provided' do + before do + entry.default = { refs: %w[branches tags] } + end + + context 'when user overrides default values' do + let(:config) { { refs: %w[feature], variables: %w[$VARIABLE] } } + + it 'does not include default values' do + expect(entry.value).to eq config + end + end + + context 'when default value has not been defined' do + let(:config) { { variables: %w[$VARIABLE] } } + + it 'includes default values' do + expect(entry.value).to eq(refs: %w[branches tags], + variables: %w[$VARIABLE]) + end + end + end + end + describe '.default' do - it 'does not have a default value' do + it 'does not have default policy' do expect(described_class.default).to be_nil end end diff --git a/spec/lib/gitlab/config/entry/configurable_spec.rb b/spec/lib/gitlab/config/entry/configurable_spec.rb index 85a7cf1d241..37e38e49c0d 100644 --- a/spec/lib/gitlab/config/entry/configurable_spec.rb +++ b/spec/lib/gitlab/config/entry/configurable_spec.rb @@ -7,6 +7,10 @@ describe Gitlab::Config::Entry::Configurable do end end + before do + allow(entry).to receive(:default) + end + describe 'validations' do context 'when entry is a hash' do let(:instance) { entry.new(key: 'value') } @@ -26,9 +30,11 @@ describe Gitlab::Config::Entry::Configurable do end describe 'configured entries' do + let(:entry_class) { double('entry_class', default: nil) } + before do - entry.class_eval do - entry :object, Object, description: 'test object' + entry.class_exec(entry_class) do |entry_class| + entry :object, entry_class, description: 'test object' end end diff --git a/spec/lib/gitlab/git/bundle_file_spec.rb b/spec/lib/gitlab/git/bundle_file_spec.rb new file mode 100644 index 00000000000..ff7c981dadd --- /dev/null +++ b/spec/lib/gitlab/git/bundle_file_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe Gitlab::Git::BundleFile do + describe '.check!' do + let(:valid_bundle) { Tempfile.new } + let(:valid_bundle_path) { valid_bundle.path } + let(:invalid_bundle_path) { Rails.root.join('spec/fixtures/malicious.bundle') } + + after do + valid_bundle.close! + end + + it 'returns nil for a valid bundle' do + valid_bundle.write("# v2 git bundle\nfoo bar baz\n") + valid_bundle.close + + expect(described_class.check!(valid_bundle_path)).to be_nil + end + + it 'raises an exception for an invalid bundle' do + expect do + described_class.check!(invalid_bundle_path) + end.to raise_error(described_class::InvalidBundleError) + end + end +end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 852ee9c96af..a19e3e84f83 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -1753,22 +1753,23 @@ describe Gitlab::Git::Repository, :seed_helper do end describe '#create_from_bundle' do - let(:bundle_path) { File.join(Dir.tmpdir, "repo-#{SecureRandom.hex}.bundle") } + let(:valid_bundle_path) { File.join(Dir.tmpdir, "repo-#{SecureRandom.hex}.bundle") } + let(:malicious_bundle_path) { Rails.root.join('spec/fixtures/malicious.bundle') } let(:project) { create(:project) } let(:imported_repo) { project.repository.raw } before do - expect(repository.bundle_to_disk(bundle_path)).to be_truthy + expect(repository.bundle_to_disk(valid_bundle_path)).to be_truthy end after do - FileUtils.rm_rf(bundle_path) + FileUtils.rm_rf(valid_bundle_path) end it 'creates a repo from a bundle file' do expect(imported_repo).not_to exist - result = imported_repo.create_from_bundle(bundle_path) + result = imported_repo.create_from_bundle(valid_bundle_path) expect(result).to be_truthy expect(imported_repo).to exist @@ -1776,11 +1777,17 @@ describe Gitlab::Git::Repository, :seed_helper do end it 'creates a symlink to the global hooks dir' do - imported_repo.create_from_bundle(bundle_path) + imported_repo.create_from_bundle(valid_bundle_path) hooks_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access { File.join(imported_repo.path, 'hooks') } expect(File.readlink(hooks_path)).to eq(Gitlab.config.gitlab_shell.hooks_path) end + + it 'raises an error if the bundle is an attempted malicious payload' do + expect do + imported_repo.create_from_bundle(malicious_bundle_path) + end.to raise_error(::Gitlab::Git::BundleFile::InvalidBundleError) + end end describe '#checksum' do diff --git a/spec/lib/gitlab/gon_helper_spec.rb b/spec/lib/gitlab/gon_helper_spec.rb index c6f09ca2112..1ff2334bacf 100644 --- a/spec/lib/gitlab/gon_helper_spec.rb +++ b/spec/lib/gitlab/gon_helper_spec.rb @@ -29,4 +29,13 @@ describe Gitlab::GonHelper do helper.push_frontend_feature_flag(:my_feature_flag, 10) end end + + describe '#default_avatar_url' do + it 'returns an absolute URL' do + url = helper.default_avatar_url + + expect(url).to match(/^http/) + expect(url).to match(/no_avatar.*png$/) + end + end end diff --git a/spec/lib/gitlab/middleware/read_only_spec.rb b/spec/lib/gitlab/middleware/read_only_spec.rb index bdb1f34d2f6..24d49a049b6 100644 --- a/spec/lib/gitlab/middleware/read_only_spec.rb +++ b/spec/lib/gitlab/middleware/read_only_spec.rb @@ -101,16 +101,36 @@ describe Gitlab::Middleware::ReadOnly do expect(subject).not_to disallow_request end - it 'expects requests to sidekiq admin to be allowed' do - response = request.post('/admin/sidekiq') + context 'sidekiq admin requests' do + where(:mounted_at) do + [ + '', + '/', + '/gitlab', + '/gitlab/', + '/gitlab/gitlab', + '/gitlab/gitlab/' + ] + end - expect(response).not_to be_redirect - expect(subject).not_to disallow_request + with_them do + before do + stub_config_setting(relative_url_root: mounted_at) + end - response = request.get('/admin/sidekiq') + it 'allows requests' do + path = File.join(mounted_at, 'admin/sidekiq') + response = request.post(path) - expect(response).not_to be_redirect - expect(subject).not_to disallow_request + expect(response).not_to be_redirect + expect(subject).not_to disallow_request + + response = request.get(path) + + expect(response).not_to be_redirect + expect(subject).not_to disallow_request + end + end end where(:description, :path) do diff --git a/spec/lib/gitlab/release_blog_post_spec.rb b/spec/lib/gitlab/release_blog_post_spec.rb new file mode 100644 index 00000000000..2c987df3767 --- /dev/null +++ b/spec/lib/gitlab/release_blog_post_spec.rb @@ -0,0 +1,97 @@ +require 'spec_helper' + +describe Gitlab::ReleaseBlogPost do + describe '.blog_post_url' do + let(:releases_xml) do + <<~EOS + <?xml version='1.0' encoding='utf-8' ?> + <feed xmlns='http://www.w3.org/2005/Atom'> + <entry> + <release>11.2</release> + <id>https://about.gitlab.com/2018/08/22/gitlab-11-2-released/</id> + </entry> + <entry> + <release>11.1</release> + <id>https://about.gitlab.com/2018/07/22/gitlab-11-1-released/</id> + </entry> + <entry> + <release>11.0</release> + <id>https://about.gitlab.com/2018/06/22/gitlab-11-0-released/</id> + </entry> + <entry> + <release>10.8</release> + <id>https://about.gitlab.com/2018/05/22/gitlab-10-8-released/</id> + </entry> + </feed> + EOS + end + + subject { described_class.send(:new).blog_post_url } + + before do + stub_request(:get, 'https://about.gitlab.com/releases.xml') + .to_return(status: 200, headers: { 'content-type' => ['text/xml'] }, body: releases_xml) + end + + context 'matches GitLab version to blog post url' do + it 'returns the correct url for major pre release' do + stub_const('Gitlab::VERSION', '11.0.0-pre') + + expect(subject).to eql('https://about.gitlab.com/2018/05/22/gitlab-10-8-released/') + end + + it 'returns the correct url for major release candidate' do + stub_const('Gitlab::VERSION', '11.0.0-rc3') + + expect(subject).to eql('https://about.gitlab.com/2018/05/22/gitlab-10-8-released/') + end + + it 'returns the correct url for major release' do + stub_const('Gitlab::VERSION', '11.0.0') + + expect(subject).to eql('https://about.gitlab.com/2018/06/22/gitlab-11-0-released/') + end + + it 'returns the correct url for minor pre release' do + stub_const('Gitlab::VERSION', '11.2.0-pre') + + expect(subject).to eql('https://about.gitlab.com/2018/07/22/gitlab-11-1-released/') + end + + it 'returns the correct url for minor release candidate' do + stub_const('Gitlab::VERSION', '11.2.0-rc3') + + expect(subject).to eql('https://about.gitlab.com/2018/07/22/gitlab-11-1-released/') + end + + it 'returns the correct url for minor release' do + stub_const('Gitlab::VERSION', '11.2.0') + + expect(subject).to eql('https://about.gitlab.com/2018/08/22/gitlab-11-2-released/') + end + + it 'returns the correct url for patch pre release' do + stub_const('Gitlab::VERSION', '11.2.1-pre') + expect(subject).to eql('https://about.gitlab.com/2018/08/22/gitlab-11-2-released/') + end + + it 'returns the correct url for patch release candidate' do + stub_const('Gitlab::VERSION', '11.2.1-rc3') + + expect(subject).to eql('https://about.gitlab.com/2018/08/22/gitlab-11-2-released/') + end + + it 'returns the correct url for patch release' do + stub_const('Gitlab::VERSION', '11.2.1') + + expect(subject).to eql('https://about.gitlab.com/2018/08/22/gitlab-11-2-released/') + end + + it 'returns nil when no blog post is matched' do + stub_const('Gitlab::VERSION', '9.0.0') + + expect(subject).to be(nil) + end + end + end +end diff --git a/spec/lib/gitlab/tracing/factory_spec.rb b/spec/lib/gitlab/tracing/factory_spec.rb new file mode 100644 index 00000000000..945490f0988 --- /dev/null +++ b/spec/lib/gitlab/tracing/factory_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' + +describe Gitlab::Tracing::Factory do + describe '.create_tracer' do + let(:service_name) { 'rspec' } + + context "when tracing is not configured" do + it 'ignores null connection strings' do + expect(described_class.create_tracer(service_name, nil)).to be_nil + end + + it 'ignores empty connection strings' do + expect(described_class.create_tracer(service_name, '')).to be_nil + end + + it 'ignores unknown implementations' do + expect(described_class.create_tracer(service_name, 'opentracing://invalid_driver')).to be_nil + end + + it 'ignores invalid connection strings' do + expect(described_class.create_tracer(service_name, 'open?tracing')).to be_nil + end + end + + context "when tracing is configured with jaeger" do + let(:mock_tracer) { double('tracer') } + + it 'processes default connections' do + expect(Gitlab::Tracing::JaegerFactory).to receive(:create_tracer).with(service_name, {}).and_return(mock_tracer) + + expect(described_class.create_tracer(service_name, 'opentracing://jaeger')).to be(mock_tracer) + end + + it 'processes connections with parameters' do + expect(Gitlab::Tracing::JaegerFactory).to receive(:create_tracer).with(service_name, { a: '1', b: '2', c: '3' }).and_return(mock_tracer) + + expect(described_class.create_tracer(service_name, 'opentracing://jaeger?a=1&b=2&c=3')).to be(mock_tracer) + end + end + end +end diff --git a/spec/lib/gitlab/tracing/jaeger_factory_spec.rb b/spec/lib/gitlab/tracing/jaeger_factory_spec.rb new file mode 100644 index 00000000000..3bffeb28830 --- /dev/null +++ b/spec/lib/gitlab/tracing/jaeger_factory_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' + +describe Gitlab::Tracing::JaegerFactory do + describe '.create_tracer' do + let(:service_name) { 'rspec' } + + it 'processes default connections' do + expect(described_class.create_tracer(service_name, {})).to respond_to(:active_span) + end + + it 'handles debug options' do + expect(described_class.create_tracer(service_name, { debug: "1" })).to respond_to(:active_span) + end + + it 'handles const sampler' do + expect(described_class.create_tracer(service_name, { sampler: "const", sampler_param: "1" })).to respond_to(:active_span) + end + + it 'handles probabilistic sampler' do + expect(described_class.create_tracer(service_name, { sampler: "probabilistic", sampler_param: "0.5" })).to respond_to(:active_span) + end + + it 'handles http_endpoint configurations' do + expect(described_class.create_tracer(service_name, { http_endpoint: "http://localhost:1234" })).to respond_to(:active_span) + end + + it 'handles udp_endpoint configurations' do + expect(described_class.create_tracer(service_name, { udp_endpoint: "localhost:4321" })).to respond_to(:active_span) + end + + it 'ignores invalid parameters' do + expect(described_class.create_tracer(service_name, { invalid: "true" })).to respond_to(:active_span) + end + + it 'accepts the debug parameter when strict_parser is set' do + expect(described_class.create_tracer(service_name, { debug: "1", strict_parsing: "1" })).to respond_to(:active_span) + end + + it 'rejects invalid parameters when strict_parser is set' do + expect { described_class.create_tracer(service_name, { invalid: "true", strict_parsing: "1" }) }.to raise_error(StandardError) + end + end +end diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb index d63f448883b..6ac3d115bc6 100644 --- a/spec/lib/gitlab_spec.rb +++ b/spec/lib/gitlab_spec.rb @@ -8,6 +8,7 @@ describe Gitlab do expect(described_class.root).to eq(Pathname.new(File.expand_path('../..', __dir__))) end end + describe '.revision' do let(:cmd) { %W[#{described_class.config.git.bin_path} log --pretty=format:%h -n 1] } @@ -69,6 +70,82 @@ describe Gitlab do end end + describe '.final_release?' do + subject { described_class.final_release? } + + context 'returns the corrent boolean value' do + it 'is false for a pre release' do + stub_const('Gitlab::VERSION', '11.0.0-pre') + + expect(subject).to be false + end + + it 'is false for a release candidate' do + stub_const('Gitlab::VERSION', '11.0.0-rc2') + + expect(subject).to be false + end + + it 'is true for a final release' do + stub_const('Gitlab::VERSION', '11.0.2') + + expect(subject).to be true + end + end + end + + describe '.minor_release' do + subject { described_class.minor_release } + + it 'returns the minor release of the full GitLab version' do + stub_const('Gitlab::VERSION', '11.0.1-rc3') + + expect(subject).to eql '11.0' + end + end + + describe '.previous_release' do + subject { described_class.previous_release } + + context 'it should return the previous release' do + it 'returns the previous major version when GitLab major version is not final' do + stub_const('Gitlab::VERSION', '11.0.1-pre') + + expect(subject).to eql '10' + end + + it 'returns the current minor version when the GitLab patch version is RC and > 0' do + stub_const('Gitlab::VERSION', '11.2.1-rc3') + + expect(subject).to eql '11.2' + end + + it 'returns the previous minor version when the GitLab patch version is RC and 0' do + stub_const('Gitlab::VERSION', '11.2.0-rc3') + + expect(subject).to eql '11.1' + end + end + end + + describe '.new_major_release?' do + subject { described_class.new_major_release? } + + context 'returns the corrent boolean value' do + it 'is true when the minor version is 0 and the patch is a pre release' do + stub_const('Gitlab::VERSION', '11.0.1-pre') + + expect(subject).to be true + end + + it 'is false when the minor version is above 0' do + stub_const('Gitlab::VERSION', '11.2.1-rc3') + + expect(subject).to be false + end + end + end + describe '.com?' do it 'is true when on GitLab.com' do stub_config_setting(url: 'https://gitlab.com') diff --git a/spec/migrations/add_foreign_keys_to_todos_spec.rb b/spec/migrations/add_foreign_keys_to_todos_spec.rb index bf2fa5c0f56..efd87173b9c 100644 --- a/spec/migrations/add_foreign_keys_to_todos_spec.rb +++ b/spec/migrations/add_foreign_keys_to_todos_spec.rb @@ -3,9 +3,11 @@ require Rails.root.join('db', 'migrate', '20180201110056_add_foreign_keys_to_tod describe AddForeignKeysToTodos, :migration do let(:todos) { table(:todos) } + let(:users) { table(:users) } + let(:projects) { table(:projects) } - let(:project) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs - let(:user) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs + let(:project) { projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce', namespace_id: 1) } + let(:user) { users.create!(email: 'email@email.com', name: 'foo', username: 'foo', projects_limit: 0) } context 'add foreign key on user_id' do let!(:todo_with_user) { create_todo(user_id: user.id) } diff --git a/spec/migrations/cleanup_legacy_artifact_migration_spec.rb b/spec/migrations/cleanup_legacy_artifact_migration_spec.rb new file mode 100644 index 00000000000..dc269d32e5a --- /dev/null +++ b/spec/migrations/cleanup_legacy_artifact_migration_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20190104182041_cleanup_legacy_artifact_migration.rb') + +describe CleanupLegacyArtifactMigration, :migration, :sidekiq, :redis do + let(:migration) { spy('migration') } + + context 'when still legacy artifacts exist' do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:pipelines) { table(:ci_pipelines) } + let(:jobs) { table(:ci_builds) } + let(:job_artifacts) { table(:ci_job_artifacts) } + let(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') } + let(:project) { projects.create!(name: 'gitlab', path: 'gitlab-ce', namespace_id: namespace.id) } + let(:pipeline) { pipelines.create!(project_id: project.id, ref: 'master', sha: 'adf43c3a') } + let(:archive_file_type) { Gitlab::BackgroundMigration::MigrateLegacyArtifacts::ARCHIVE_FILE_TYPE } + let(:metadata_file_type) { Gitlab::BackgroundMigration::MigrateLegacyArtifacts::METADATA_FILE_TYPE } + let(:local_store) { ::ObjectStorage::Store::LOCAL } + let(:remote_store) { ::ObjectStorage::Store::REMOTE } + let(:legacy_location) { Gitlab::BackgroundMigration::MigrateLegacyArtifacts::LEGACY_PATH_FILE_LOCATION } + + before do + jobs.create!(id: 1, commit_id: pipeline.id, project_id: project.id, status: :success, artifacts_file: 'archive.zip') + jobs.create!(id: 2, commit_id: pipeline.id, project_id: project.id, status: :failed, artifacts_metadata: 'metadata.gz') + jobs.create!(id: 3, commit_id: pipeline.id, project_id: project.id, status: :failed, artifacts_file: 'archive.zip', artifacts_metadata: 'metadata.gz') + jobs.create!(id: 4, commit_id: pipeline.id, project_id: project.id, status: :running) + jobs.create!(id: 5, commit_id: pipeline.id, project_id: project.id, status: :success, artifacts_file: 'archive.zip', artifacts_file_store: remote_store, artifacts_metadata: 'metadata.gz') + jobs.create!(id: 6, commit_id: pipeline.id, project_id: project.id, status: :failed, artifacts_file: 'archive.zip', artifacts_metadata: 'metadata.gz') + end + + it 'steals sidekiq jobs from MigrateLegacyArtifacts background migration' do + expect(Gitlab::BackgroundMigration).to receive(:steal).with('MigrateLegacyArtifacts') + + migrate! + end + + it 'migrates legacy artifacts to ci_job_artifacts table' do + migrate! + + expect(job_artifacts.order(:job_id, :file_type).pluck('project_id, job_id, file_type, file_store, size, expire_at, file, file_sha256, file_location')) + .to eq([[project.id, 1, archive_file_type, local_store, nil, nil, 'archive.zip', nil, legacy_location], + [project.id, 3, archive_file_type, local_store, nil, nil, 'archive.zip', nil, legacy_location], + [project.id, 3, metadata_file_type, local_store, nil, nil, 'metadata.gz', nil, legacy_location], + [project.id, 5, archive_file_type, remote_store, nil, nil, 'archive.zip', nil, legacy_location], + [project.id, 5, metadata_file_type, local_store, nil, nil, 'metadata.gz', nil, legacy_location], + [project.id, 6, archive_file_type, local_store, nil, nil, 'archive.zip', nil, legacy_location], + [project.id, 6, metadata_file_type, local_store, nil, nil, 'metadata.gz', nil, legacy_location]]) + end + end +end diff --git a/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb b/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb index b5980cb9ddb..651341906c2 100644 --- a/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb +++ b/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb @@ -2,6 +2,8 @@ require 'spec_helper' require Rails.root.join('db', 'post_migrate', '20170502101023_cleanup_namespaceless_pending_delete_projects.rb') describe CleanupNamespacelessPendingDeleteProjects, :migration, schema: 20180222043024 do + let(:projects) { table(:projects) } + before do # Stub after_save callbacks that will fail when Project has no namespace allow_any_instance_of(Project).to receive(:ensure_storage_path_exists).and_return(nil) @@ -10,9 +12,9 @@ describe CleanupNamespacelessPendingDeleteProjects, :migration, schema: 20180222 describe '#up' do it 'only cleans up pending delete projects' do - create(:project) # rubocop:disable RSpec/FactoriesInMigrationSpecs - create(:project, pending_delete: true) # rubocop:disable RSpec/FactoriesInMigrationSpecs - project = build(:project, pending_delete: true, namespace_id: nil) # rubocop:disable RSpec/FactoriesInMigrationSpecs + projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce', namespace_id: 1) + projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ee', namespace_id: 2, pending_delete: true) + project = Project.new(pending_delete: true, namespace_id: nil) project.save(validate: false) expect(NamespacelessProjectDestroyWorker).to receive(:bulk_perform_async).with([[project.id]]) @@ -21,8 +23,8 @@ describe CleanupNamespacelessPendingDeleteProjects, :migration, schema: 20180222 end it 'does nothing when no pending delete projects without namespace found' do - create(:project) # rubocop:disable RSpec/FactoriesInMigrationSpecs - create(:project, pending_delete: true) # rubocop:disable RSpec/FactoriesInMigrationSpecs + projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce', namespace_id: 1) + projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ee', namespace_id: 2, pending_delete: true) expect(NamespacelessProjectDestroyWorker).not_to receive(:bulk_perform_async) diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb index de313a8ca36..52c347229c6 100644 --- a/spec/models/clusters/applications/ingress_spec.rb +++ b/spec/models/clusters/applications/ingress_spec.rb @@ -35,7 +35,7 @@ describe Clusters::Applications::Ingress do let(:application) { create(:clusters_applications_ingress, :scheduled, version: '0.22.0') } it 'updates the application version' do - expect(application.reload.version).to eq('0.23.0') + expect(application.reload.version).to eq('1.1.2') end end end @@ -90,7 +90,7 @@ describe Clusters::Applications::Ingress do it 'should be initialized with ingress arguments' do expect(subject.name).to eq('ingress') expect(subject.chart).to eq('stable/nginx-ingress') - expect(subject.version).to eq('0.23.0') + expect(subject.version).to eq('1.1.2') expect(subject).to be_rbac expect(subject.files).to eq(ingress.files) end @@ -107,7 +107,7 @@ describe Clusters::Applications::Ingress do let(:ingress) { create(:clusters_applications_ingress, :errored, version: 'nginx') } it 'should be initialized with the locked version' do - expect(subject.version).to eq('0.23.0') + expect(subject.version).to eq('1.1.2') end end end diff --git a/spec/models/concerns/manual_inverse_association_spec.rb b/spec/models/concerns/manual_inverse_association_spec.rb index aad40883854..ff4a04ea573 100644 --- a/spec/models/concerns/manual_inverse_association_spec.rb +++ b/spec/models/concerns/manual_inverse_association_spec.rb @@ -32,10 +32,10 @@ describe ManualInverseAssociation do .not_to exceed_query_limit(0) end - it 'passes arguments to the default association method, to allow reloading' do + it 'allows reloading the relation' do query_count = ActiveRecord::QueryRecorder.new do instance.manual_association - instance.manual_association(true) + instance.reload_manual_association end.count expect(query_count).to eq(2) diff --git a/spec/models/gpg_key_spec.rb b/spec/models/gpg_key_spec.rb index 33e6f1de3d1..58a1d2e4ea2 100644 --- a/spec/models/gpg_key_spec.rb +++ b/spec/models/gpg_key_spec.rb @@ -199,7 +199,7 @@ describe GpgKey do gpg_key.revoke - expect(gpg_key.subkeys(true)).to be_blank + expect(gpg_key.subkeys.reload).to be_blank end it 'invalidates all signatures associated to the subkeys' do diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index e18b29df321..bfc9035cb56 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1418,6 +1418,23 @@ describe MergeRequest do .to change { merge_request.reload.head_pipeline } .from(nil).to(pipeline) end + + context 'when merge request has already had head pipeline' do + before do + merge_request.update!(head_pipeline: pipeline) + end + + context 'when failed to find an actual head pipeline' do + before do + allow(merge_request).to receive(:find_actual_head_pipeline) { } + end + + it 'does not update the current head pipeline' do + expect { subject } + .not_to change { merge_request.reload.head_pipeline } + end + end + end end context 'when there are no pipelines with the diff head sha' do diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index b3d31e65c85..2e436f2cc8a 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -240,7 +240,88 @@ describe Milestone do end end - describe '.upcoming_ids_by_projects' do + describe '#for_projects_and_groups' do + let(:project) { create(:project) } + let(:project_other) { create(:project) } + let(:group) { create(:group) } + let(:group_other) { create(:group) } + + before do + create(:milestone, project: project) + create(:milestone, project: project_other) + create(:milestone, group: group) + create(:milestone, group: group_other) + end + + subject { described_class.for_projects_and_groups(projects, groups) } + + shared_examples 'filters by projects and groups' do + it 'returns milestones filtered by project' do + milestones = described_class.for_projects_and_groups(projects, []) + + expect(milestones.count).to eq(1) + expect(milestones.first.project_id).to eq(project.id) + end + + it 'returns milestones filtered by group' do + milestones = described_class.for_projects_and_groups([], groups) + + expect(milestones.count).to eq(1) + expect(milestones.first.group_id).to eq(group.id) + end + + it 'returns milestones filtered by both project and group' do + milestones = described_class.for_projects_and_groups(projects, groups) + + expect(milestones.count).to eq(2) + expect(milestones).to contain_exactly(project.milestones.first, group.milestones.first) + end + end + + context 'ids as params' do + let(:projects) { [project.id] } + let(:groups) { [group.id] } + + it_behaves_like 'filters by projects and groups' + end + + context 'relations as params' do + let(:projects) { Project.where(id: project.id).select(:id) } + let(:groups) { Group.where(id: group.id).select(:id) } + + it_behaves_like 'filters by projects and groups' + end + + context 'objects as params' do + let(:projects) { [project] } + let(:groups) { [group] } + + it_behaves_like 'filters by projects and groups' + end + + it 'returns no records if projects and groups are nil' do + milestones = described_class.for_projects_and_groups(nil, nil) + + expect(milestones).to be_empty + end + end + + describe '.upcoming_ids' do + let(:group_1) { create(:group) } + let(:group_2) { create(:group) } + let(:group_3) { create(:group) } + let(:groups) { [group_1, group_2, group_3] } + + let!(:past_milestone_group_1) { create(:milestone, group: group_1, due_date: Time.now - 1.day) } + let!(:current_milestone_group_1) { create(:milestone, group: group_1, due_date: Time.now + 1.day) } + let!(:future_milestone_group_1) { create(:milestone, group: group_1, due_date: Time.now + 2.days) } + + let!(:past_milestone_group_2) { create(:milestone, group: group_2, due_date: Time.now - 1.day) } + let!(:closed_milestone_group_2) { create(:milestone, :closed, group: group_2, due_date: Time.now + 1.day) } + let!(:current_milestone_group_2) { create(:milestone, group: group_2, due_date: Time.now + 2.days) } + + let!(:past_milestone_group_3) { create(:milestone, group: group_3, due_date: Time.now - 1.day) } + let(:project_1) { create(:project) } let(:project_2) { create(:project) } let(:project_3) { create(:project) } @@ -256,16 +337,20 @@ describe Milestone do let!(:past_milestone_project_3) { create(:milestone, project: project_3, due_date: Time.now - 1.day) } - # The call to `#try` is because this returns a relation with a Postgres DB, - # and an array of IDs with a MySQL DB. - let(:milestone_ids) { described_class.upcoming_ids_by_projects(projects).map { |id| id.try(:id) || id } } + let(:milestone_ids) { described_class.upcoming_ids(projects, groups).map(&:id) } - it 'returns the next upcoming open milestone ID for each project' do - expect(milestone_ids).to contain_exactly(current_milestone_project_1.id, current_milestone_project_2.id) + it 'returns the next upcoming open milestone ID for each project and group' do + expect(milestone_ids).to contain_exactly( + current_milestone_project_1.id, + current_milestone_project_2.id, + current_milestone_group_1.id, + current_milestone_group_2.id + ) end - context 'when the projects have no open upcoming milestones' do + context 'when the projects and groups have no open upcoming milestones' do let(:projects) { [project_3] } + let(:groups) { [group_3] } it 'returns no results' do expect(milestone_ids).to be_empty diff --git a/spec/models/project_import_data_spec.rb b/spec/models/project_import_data_spec.rb index e9910c0a5d1..fe47811f074 100644 --- a/spec/models/project_import_data_spec.rb +++ b/spec/models/project_import_data_spec.rb @@ -39,4 +39,15 @@ describe ProjectImportData do expect(row.credentials).to eq({ 'number' => 10, 'foo' => 'bar' }) end end + + describe '#clear_credentials' do + it 'clears out the Hash' do + row = described_class.new + + row.merge_credentials('number' => 10) + row.clear_credentials + + expect(row.credentials).to eq({}) + end + end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 3044150bca8..7a8dc59039e 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -209,9 +209,14 @@ describe Project do it 'does not allow new projects beyond user limits' do project2 = build(:project) - allow(project2).to receive(:creator).and_return(double(can_create_project?: false, projects_limit: 0).as_null_object) + + allow(project2) + .to receive(:creator) + .and_return( + double(can_create_project?: false, projects_limit: 0).as_null_object + ) + expect(project2).not_to be_valid - expect(project2.errors[:limit_reached].first).to match(/Personal project creation is not allowed/) end describe 'wiki path conflict' do @@ -1413,6 +1418,24 @@ describe Project do end end + describe '#visibility_level' do + let(:project) { build(:project) } + + subject { project.visibility_level } + + context 'by default' do + it { is_expected.to eq(Gitlab::VisibilityLevel::PRIVATE) } + end + + context 'when set to INTERNAL in application settings' do + before do + stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL) + end + + it { is_expected.to eq(Gitlab::VisibilityLevel::INTERNAL) } + end + end + describe '#visibility_level_allowed?' do let(:project) { create(:project, :internal) } @@ -2026,29 +2049,6 @@ describe Project do end end - describe '#get_build' do - let(:project) { create(:project, :repository) } - let(:ci_pipeline) { create(:ci_pipeline, project: project) } - - context 'when build exists' do - context 'build is associated with project' do - let(:build) { create(:ci_build, :success, pipeline: ci_pipeline) } - - it { expect(project.get_build(build.id)).to eq(build) } - end - - context 'build is not associated with project' do - let(:build) { create(:ci_build, :success) } - - it { expect(project.get_build(build.id)).to be_nil } - end - end - - context 'build does not exists' do - it { expect(project.get_build(rand 100)).to be_nil } - end - end - describe '#import_status' do context 'with import_state' do it 'returns the right status' do @@ -3092,7 +3092,7 @@ describe Project do context 'when the project is in a subgroup' do let(:group) { create(:group, :nested) } - it { is_expected.to be(false) } + it { is_expected.to be(true) } end end @@ -4425,6 +4425,86 @@ describe Project do end end + describe '#leave_pool_repository' do + let(:pool) { create(:pool_repository) } + let(:project) { create(:project, :repository, pool_repository: pool) } + + it 'removes the membership' do + project.leave_pool_repository + + expect(pool.member_projects.reload).not_to include(project) + end + end + + describe '#check_personal_projects_limit' do + context 'when creating a project for a group' do + it 'does nothing' do + creator = build(:user) + project = build(:project, namespace: build(:group), creator: creator) + + allow(creator) + .to receive(:can_create_project?) + .and_return(false) + + project.check_personal_projects_limit + + expect(project.errors).to be_empty + end + end + + context 'when the user is not allowed to create a personal project' do + let(:user) { build(:user) } + let(:project) { build(:project, creator: user) } + + before do + allow(user) + .to receive(:can_create_project?) + .and_return(false) + end + + context 'when the project limit is zero' do + it 'adds a validation error' do + allow(user) + .to receive(:projects_limit) + .and_return(0) + + project.check_personal_projects_limit + + expect(project.errors[:limit_reached].first) + .to match(/Personal project creation is not allowed/) + end + end + + context 'when the project limit is greater than zero' do + it 'adds a validation error' do + allow(user) + .to receive(:projects_limit) + .and_return(5) + + project.check_personal_projects_limit + + expect(project.errors[:limit_reached].first) + .to match(/Your project limit is 5 projects/) + end + end + end + + context 'when the user is allowed to create personal projects' do + it 'does nothing' do + user = build(:user) + project = build(:project, creator: user) + + allow(user) + .to receive(:can_create_project?) + .and_return(true) + + project.check_personal_projects_limit + + expect(project.errors).to be_empty + end + end + end + def rugged_config rugged_repo(project.repository).config end diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb index 224bc9ed935..c06e9a08ab4 100644 --- a/spec/models/remote_mirror_spec.rb +++ b/spec/models/remote_mirror_spec.rb @@ -303,6 +303,25 @@ describe RemoteMirror, :mailer do end end + context '#url=' do + let(:remote_mirror) { create(:project, :repository, :remote_mirror).remote_mirrors.first } + + it 'resets all the columns when URL changes' do + remote_mirror.update(last_error: Time.now, + last_update_at: Time.now, + last_successful_update_at: Time.now, + update_status: 'started', + error_notification_sent: true) + + expect { remote_mirror.update_attribute(:url, 'http://new.example.com') } + .to change { remote_mirror.last_error }.to(nil) + .and change { remote_mirror.last_update_at }.to(nil) + .and change { remote_mirror.last_successful_update_at }.to(nil) + .and change { remote_mirror.update_status }.to('finished') + .and change { remote_mirror.error_notification_sent }.to(false) + end + end + context '#updated_since?' do let(:remote_mirror) { create(:project, :repository, :remote_mirror).remote_mirrors.first } let(:timestamp) { Time.now - 5.minutes } diff --git a/spec/requests/api/features_spec.rb b/spec/requests/api/features_spec.rb index 7d3eff7d32d..22a9e36ca31 100644 --- a/spec/requests/api/features_spec.rb +++ b/spec/requests/api/features_spec.rb @@ -129,6 +129,40 @@ describe API::Features do end end + context 'when enabling for a project by path' do + context 'when the project exists' do + let!(:project) { create(:project) } + + it 'sets the feature gate' do + post api("/features/#{feature_name}", admin), params: { value: 'true', project: project.full_path } + + expect(response).to have_gitlab_http_status(201) + expect(json_response).to eq( + 'name' => 'my_feature', + 'state' => 'conditional', + 'gates' => [ + { 'key' => 'boolean', 'value' => false }, + { 'key' => 'actors', 'value' => ["Project:#{project.id}"] } + ]) + end + end + + context 'when the project does not exist' do + it 'sets no new values' do + post api("/features/#{feature_name}", admin), params: { value: 'true', project: 'mep/to/the/mep/mep' } + + expect(response).to have_gitlab_http_status(201) + expect(json_response).to eq( + "name" => "my_feature", + "state" => "off", + "gates" => [ + { "key" => "boolean", "value" => false } + ] + ) + end + end + end + it 'creates a feature with the given percentage if passed an integer' do post api("/features/#{feature_name}", admin), params: { value: '50' } diff --git a/spec/requests/api/import_github_spec.rb b/spec/requests/api/import_github_spec.rb new file mode 100644 index 00000000000..aceff9b4aa6 --- /dev/null +++ b/spec/requests/api/import_github_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +describe API::ImportGithub do + include ApiHelpers + + let(:token) { "asdasd12345" } + let(:provider) { :github } + let(:access_params) { { github_access_token: token } } + + describe "POST /import/github" do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:provider_username) { user.username } + let(:provider_user) { OpenStruct.new(login: provider_username) } + let(:provider_repo) do + OpenStruct.new( + name: 'vim', + full_name: "#{provider_username}/vim", + owner: OpenStruct.new(login: provider_username) + ) + end + + before do + Grape::Endpoint.before_each do |endpoint| + allow(endpoint).to receive(:client).and_return(double('client', user: provider_user, repo: provider_repo).as_null_object) + end + end + + it 'returns 201 response when the project is imported successfully' do + allow(Gitlab::LegacyGithubImport::ProjectCreator) + .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider) + .and_return(double(execute: project)) + + post api("/import/github", user), params: { + target_namespace: user.namespace_path, + personal_access_token: token, + repo_id: 1234 + } + expect(response).to have_gitlab_http_status(201) + expect(json_response).to be_a Hash + expect(json_response['name']).to eq(project.name) + end + + it 'returns 422 response when user can not create projects in the chosen namespace' do + other_namespace = create(:group, name: 'other_namespace') + + post api("/import/github", user), params: { + target_namespace: other_namespace.name, + personal_access_token: token, + repo_id: 1234 + } + + expect(response).to have_gitlab_http_status(422) + end + end +end diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 0fd465da4f8..ba7930f6c9d 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -92,12 +92,10 @@ describe API::Issues do end context "when authenticated" do - let(:first_issue) { json_response.first } - it "returns an array of issues" do get api("/issues", user) - expect_paginated_array_response(size: 2) + expect_paginated_array_response([issue.id, closed_issue.id]) expect(json_response.first['title']).to eq(issue.title) expect(json_response.last).to have_key('web_url') end @@ -105,23 +103,19 @@ describe API::Issues do it 'returns an array of closed issues' do get api('/issues', user), params: { state: :closed } - expect_paginated_array_response(size: 1) - expect(first_issue['id']).to eq(closed_issue.id) + expect_paginated_array_response(closed_issue.id) end it 'returns an array of opened issues' do get api('/issues', user), params: { state: :opened } - expect_paginated_array_response(size: 1) - expect(first_issue['id']).to eq(issue.id) + expect_paginated_array_response(issue.id) end it 'returns an array of all issues' do get api('/issues', user), params: { state: :all } - expect_paginated_array_response(size: 2) - expect(first_issue['id']).to eq(issue.id) - expect(json_response.second['id']).to eq(closed_issue.id) + expect_paginated_array_response([issue.id, closed_issue.id]) end it 'returns issues assigned to me' do @@ -129,8 +123,7 @@ describe API::Issues do get api('/issues', user2), params: { scope: 'assigned_to_me' } - expect_paginated_array_response(size: 1) - expect(first_issue['id']).to eq(issue2.id) + expect_paginated_array_response(issue2.id) end it 'returns issues assigned to me (kebab-case)' do @@ -138,8 +131,7 @@ describe API::Issues do get api('/issues', user2), params: { scope: 'assigned-to-me' } - expect_paginated_array_response(size: 1) - expect(first_issue['id']).to eq(issue2.id) + expect_paginated_array_response(issue2.id) end it 'returns issues authored by the given author id' do @@ -147,8 +139,7 @@ describe API::Issues do get api('/issues', user), params: { author_id: user2.id, scope: 'all' } - expect_paginated_array_response(size: 1) - expect(first_issue['id']).to eq(issue2.id) + expect_paginated_array_response(issue2.id) end it 'returns issues assigned to the given assignee id' do @@ -156,8 +147,7 @@ describe API::Issues do get api('/issues', user), params: { assignee_id: user2.id, scope: 'all' } - expect_paginated_array_response(size: 1) - expect(first_issue['id']).to eq(issue2.id) + expect_paginated_array_response(issue2.id) end it 'returns issues authored by the given author id and assigned to the given assignee id' do @@ -165,8 +155,7 @@ describe API::Issues do get api('/issues', user), params: { author_id: user2.id, assignee_id: user2.id, scope: 'all' } - expect_paginated_array_response(size: 1) - expect(first_issue['id']).to eq(issue2.id) + expect_paginated_array_response(issue2.id) end it 'returns issues with no assignee' do @@ -174,8 +163,7 @@ describe API::Issues do get api('/issues', user), params: { assignee_id: 0, scope: 'all' } - expect_paginated_array_response(size: 1) - expect(first_issue['id']).to eq(issue2.id) + expect_paginated_array_response(issue2.id) end it 'returns issues with no assignee' do @@ -183,8 +171,7 @@ describe API::Issues do get api('/issues', user), params: { assignee_id: 'None', scope: 'all' } - expect_paginated_array_response(size: 1) - expect(first_issue['id']).to eq(issue2.id) + expect_paginated_array_response(issue2.id) end it 'returns issues with any assignee' do @@ -193,18 +180,17 @@ describe API::Issues do get api('/issues', user), params: { assignee_id: 'Any', scope: 'all' } - expect_paginated_array_response(size: 3) + expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) end it 'returns issues reacted by the authenticated user' do issue2 = create(:issue, project: project, author: user, assignees: [user]) create(:award_emoji, awardable: issue2, user: user2, name: 'star') - create(:award_emoji, awardable: issue, user: user2, name: 'thumbsup') get api('/issues', user2), params: { my_reaction_emoji: 'Any', scope: 'all' } - expect_paginated_array_response(size: 2) + expect_paginated_array_response([issue2.id, issue.id]) end it 'returns issues not reacted by the authenticated user' do @@ -213,21 +199,19 @@ describe API::Issues do get api('/issues', user2), params: { my_reaction_emoji: 'None', scope: 'all' } - expect_paginated_array_response(size: 2) + expect_paginated_array_response([issue.id, closed_issue.id]) end it 'returns issues matching given search string for title' do get api("/issues", user), params: { search: issue.title } - expect_paginated_array_response(size: 1) - expect(json_response.first['id']).to eq(issue.id) + expect_paginated_array_response(issue.id) end it 'returns issues matching given search string for description' do get api("/issues", user), params: { search: issue.description } - expect_paginated_array_response(size: 1) - expect(first_issue['id']).to eq(issue.id) + expect_paginated_array_response(issue.id) end context 'filtering before a specific date' do @@ -236,15 +220,13 @@ describe API::Issues do it 'returns issues created before a specific date' do get api('/issues?created_before=2000-01-02T00:00:00.060Z', user) - expect(json_response.size).to eq(1) - expect(first_issue['id']).to eq(issue2.id) + expect_paginated_array_response(issue2.id) end it 'returns issues updated before a specific date' do get api('/issues?updated_before=2000-01-02T00:00:00.060Z', user) - expect(json_response.size).to eq(1) - expect(first_issue['id']).to eq(issue2.id) + expect_paginated_array_response(issue2.id) end end @@ -254,23 +236,21 @@ describe API::Issues do it 'returns issues created after a specific date' do get api("/issues?created_after=#{issue2.created_at}", user) - expect(json_response.size).to eq(1) - expect(first_issue['id']).to eq(issue2.id) + expect_paginated_array_response(issue2.id) end it 'returns issues updated after a specific date' do get api("/issues?updated_after=#{issue2.updated_at}", user) - expect(json_response.size).to eq(1) - expect(first_issue['id']).to eq(issue2.id) + expect_paginated_array_response(issue2.id) end end it 'returns an array of labeled issues' do get api("/issues", user), params: { labels: label.title } - expect_paginated_array_response(size: 1) - expect(first_issue['labels']).to eq([label.title]) + expect_paginated_array_response(issue.id) + expect(json_response.first['labels']).to eq([label.title]) end it 'returns an array of labeled issues when all labels matches' do @@ -282,20 +262,20 @@ describe API::Issues do get api("/issues", user), params: { labels: "#{label.title},#{label_b.title},#{label_c.title}" } - expect_paginated_array_response(size: 1) + expect_paginated_array_response(issue.id) expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title]) end it 'returns an empty array if no issue matches labels' do get api('/issues', user), params: { labels: 'foo,bar' } - expect_paginated_array_response(size: 0) + expect_paginated_array_response([]) end it 'returns an array of labeled issues matching given state' do get api("/issues", user), params: { labels: label.title, state: :opened } - expect_paginated_array_response(size: 1) + expect_paginated_array_response(issue.id) expect(json_response.first['labels']).to eq([label.title]) expect(json_response.first['state']).to eq('opened') end @@ -303,112 +283,96 @@ describe API::Issues do it 'returns an empty array if no issue matches labels and state filters' do get api("/issues", user), params: { labels: label.title, state: :closed } - expect_paginated_array_response(size: 0) + expect_paginated_array_response([]) end it 'returns an array of issues with any label' do get api("/issues", user), params: { labels: IssuesFinder::FILTER_ANY } - expect_paginated_array_response(size: 1) - expect(json_response.first['id']).to eq(issue.id) + expect_paginated_array_response(issue.id) end it 'returns an array of issues with no label' do get api("/issues", user), params: { labels: IssuesFinder::FILTER_NONE } - expect_paginated_array_response(size: 1) - expect(json_response.first['id']).to eq(closed_issue.id) + expect_paginated_array_response(closed_issue.id) end it 'returns an array of issues with no label when using the legacy No+Label filter' do get api("/issues", user), params: { labels: "No Label" } - expect_paginated_array_response(size: 1) - expect(json_response.first['id']).to eq(closed_issue.id) + expect_paginated_array_response(closed_issue.id) end it 'returns an empty array if no issue matches milestone' do get api("/issues?milestone=#{empty_milestone.title}", user) - expect_paginated_array_response(size: 0) + expect_paginated_array_response([]) end it 'returns an empty array if milestone does not exist' do get api("/issues?milestone=foo", user) - expect_paginated_array_response(size: 0) + expect_paginated_array_response([]) end it 'returns an array of issues in given milestone' do get api("/issues?milestone=#{milestone.title}", user) - expect_paginated_array_response(size: 2) - expect(json_response.first['id']).to eq(issue.id) - expect(json_response.second['id']).to eq(closed_issue.id) + expect_paginated_array_response([issue.id, closed_issue.id]) end it 'returns an array of issues matching state in milestone' do get api("/issues?milestone=#{milestone.title}"\ '&state=closed', user) - expect_paginated_array_response(size: 1) - expect(json_response.first['id']).to eq(closed_issue.id) + expect_paginated_array_response(closed_issue.id) end it 'returns an array of issues with no milestone' do get api("/issues?milestone=#{no_milestone_title}", author) - expect_paginated_array_response(size: 1) - expect(json_response.first['id']).to eq(confidential_issue.id) + expect_paginated_array_response(confidential_issue.id) end it 'returns an array of issues found by iids' do get api('/issues', user), params: { iids: [closed_issue.iid] } - expect_paginated_array_response(size: 1) - expect(json_response.first['id']).to eq(closed_issue.id) + expect_paginated_array_response(closed_issue.id) end it 'returns an empty array if iid does not exist' do get api("/issues", user), params: { iids: [99999] } - expect_paginated_array_response(size: 0) + expect_paginated_array_response([]) end it 'sorts by created_at descending by default' do get api('/issues', user) - response_dates = json_response.map { |issue| issue['created_at'] } - - expect_paginated_array_response(size: 2) - expect(response_dates).to eq(response_dates.sort.reverse) + expect_paginated_array_response([issue.id, closed_issue.id]) end it 'sorts ascending when requested' do get api('/issues?sort=asc', user) - response_dates = json_response.map { |issue| issue['created_at'] } - - expect_paginated_array_response(size: 2) - expect(response_dates).to eq(response_dates.sort) + expect_paginated_array_response([closed_issue.id, issue.id]) end it 'sorts by updated_at descending when requested' do get api('/issues?order_by=updated_at', user) - response_dates = json_response.map { |issue| issue['updated_at'] } + issue.touch(:updated_at) - expect_paginated_array_response(size: 2) - expect(response_dates).to eq(response_dates.sort.reverse) + expect_paginated_array_response([issue.id, closed_issue.id]) end it 'sorts by updated_at ascending when requested' do get api('/issues?order_by=updated_at&sort=asc', user) - response_dates = json_response.map { |issue| issue['updated_at'] } + issue.touch(:updated_at) - expect_paginated_array_response(size: 2) - expect(response_dates).to eq(response_dates.sort) + expect_paginated_array_response([closed_issue.id, issue.id]) end it 'matches V4 response schema' do @@ -430,7 +394,8 @@ describe API::Issues do project: group_project, state: :closed, milestone: group_milestone, - updated_at: 3.hours.ago + updated_at: 3.hours.ago, + created_at: 1.day.ago end let!(:group_confidential_issue) do create :issue, @@ -438,7 +403,8 @@ describe API::Issues do project: group_project, author: author, assignees: [assignee], - updated_at: 2.hours.ago + updated_at: 2.hours.ago, + created_at: 2.days.ago end let!(:group_issue) do create :issue, @@ -448,7 +414,8 @@ describe API::Issues do milestone: group_milestone, updated_at: 1.hour.ago, title: issue_title, - description: issue_description + description: issue_description, + created_at: 5.days.ago end let!(:group_label) do create(:label, title: 'group_lbl', color: '#FFAABB', project: group_project) @@ -479,10 +446,7 @@ describe API::Issues do it 'also returns subgroups projects issues' do get api(base_url, user) - issue_ids = json_response.map { |issue| issue['id'] } - - expect_paginated_array_response(size: 5) - expect(issue_ids).to include(issue_1.id, issue_2.id) + expect_paginated_array_response([issue_2.id, issue_1.id, group_closed_issue.id, group_confidential_issue.id, group_issue.id]) end end @@ -490,7 +454,7 @@ describe API::Issues do it 'lists all issues in public projects' do get api(base_url) - expect_paginated_array_response(size: 2) + expect_paginated_array_response([group_closed_issue.id, group_issue.id]) end end @@ -502,65 +466,62 @@ describe API::Issues do it 'returns all group issues (including opened and closed)' do get api(base_url, admin) - expect_paginated_array_response(size: 3) + expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue.id]) end it 'returns group issues without confidential issues for non project members' do get api(base_url, non_member), params: { state: :opened } - expect_paginated_array_response(size: 1) - expect(json_response.first['title']).to eq(group_issue.title) + expect_paginated_array_response(group_issue.id) end it 'returns group confidential issues for author' do get api(base_url, author), params: { state: :opened } - expect_paginated_array_response(size: 2) + expect_paginated_array_response([group_confidential_issue.id, group_issue.id]) end it 'returns group confidential issues for assignee' do get api(base_url, assignee), params: { state: :opened } - expect_paginated_array_response(size: 2) + expect_paginated_array_response([group_confidential_issue.id, group_issue.id]) end it 'returns group issues with confidential issues for project members' do get api(base_url, user), params: { state: :opened } - expect_paginated_array_response(size: 2) + expect_paginated_array_response([group_confidential_issue.id, group_issue.id]) end it 'returns group confidential issues for admin' do get api(base_url, admin), params: { state: :opened } - expect_paginated_array_response(size: 2) + expect_paginated_array_response([group_confidential_issue.id, group_issue.id]) end it 'returns an array of labeled group issues' do get api(base_url, user), params: { labels: group_label.title } - expect_paginated_array_response(size: 1) + expect_paginated_array_response(group_issue.id) expect(json_response.first['labels']).to eq([group_label.title]) end it 'returns an array of labeled group issues where all labels match' do get api(base_url, user), params: { labels: "#{group_label.title},foo,bar" } - expect_paginated_array_response(size: 0) + expect_paginated_array_response([]) end it 'returns issues matching given search string for title' do get api(base_url, user), params: { search: group_issue.title } - expect_paginated_array_response(size: 1) - expect(json_response.first['id']).to eq(group_issue.id) + expect_paginated_array_response(group_issue.id) end it 'returns issues matching given search string for description' do get api(base_url, user), params: { search: group_issue.description } - expect_paginated_array_response(size: 1) - expect(json_response.first['id']).to eq(group_issue.id) + expect_paginated_array_response(group_issue.id) end it 'returns an array of labeled issues when all labels matches' do @@ -572,69 +533,64 @@ describe API::Issues do get api(base_url, user), params: { labels: "#{group_label.title},#{label_b.title},#{label_c.title}" } - expect_paginated_array_response(size: 1) + expect_paginated_array_response(group_issue.id) expect(json_response.first['labels']).to eq([label_c.title, label_b.title, group_label.title]) end it 'returns an array of issues found by iids' do get api(base_url, user), params: { iids: [group_issue.iid] } - expect_paginated_array_response(size: 1) + expect_paginated_array_response(group_issue.id) expect(json_response.first['id']).to eq(group_issue.id) end it 'returns an empty array if iid does not exist' do get api(base_url, user), params: { iids: [99999] } - expect_paginated_array_response(size: 0) + expect_paginated_array_response([]) end it 'returns an empty array if no group issue matches labels' do get api(base_url, user), params: { labels: 'foo,bar' } - expect_paginated_array_response(size: 0) + expect_paginated_array_response([]) end it 'returns an array of group issues with any label' do get api(base_url, user), params: { labels: IssuesFinder::FILTER_ANY } - expect_paginated_array_response(size: 1) + expect_paginated_array_response(group_issue.id) expect(json_response.first['id']).to eq(group_issue.id) end it 'returns an array of group issues with no label' do get api(base_url, user), params: { labels: IssuesFinder::FILTER_NONE } - response_ids = json_response.map { |issue| issue['id'] } - - expect_paginated_array_response(size: 2) - expect(response_ids).to contain_exactly(group_closed_issue.id, group_confidential_issue.id) + expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id]) end it 'returns an empty array if no issue matches milestone' do get api(base_url, user), params: { milestone: group_empty_milestone.title } - expect_paginated_array_response(size: 0) + expect_paginated_array_response([]) end it 'returns an empty array if milestone does not exist' do get api(base_url, user), params: { milestone: 'foo' } - expect_paginated_array_response(size: 0) + expect_paginated_array_response([]) end it 'returns an array of issues in given milestone' do get api(base_url, user), params: { state: :opened, milestone: group_milestone.title } - expect_paginated_array_response(size: 1) - expect(json_response.first['id']).to eq(group_issue.id) + expect_paginated_array_response(group_issue.id) end it 'returns an array of issues matching state in milestone' do get api(base_url, user), params: { milestone: group_milestone.title, state: :closed } - expect_paginated_array_response(size: 1) - expect(json_response.first['id']).to eq(group_closed_issue.id) + expect_paginated_array_response(group_closed_issue.id) end it 'returns an array of issues with no milestone' do @@ -642,44 +598,33 @@ describe API::Issues do expect(response).to have_gitlab_http_status(200) - expect_paginated_array_response(size: 1) - expect(json_response.first['id']).to eq(group_confidential_issue.id) + expect_paginated_array_response(group_confidential_issue.id) end it 'sorts by created_at descending by default' do get api(base_url, user) - response_dates = json_response.map { |issue| issue['created_at'] } - - expect_paginated_array_response(size: 3) - expect(response_dates).to eq(response_dates.sort.reverse) + expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue.id]) end it 'sorts ascending when requested' do get api("#{base_url}?sort=asc", user) - response_dates = json_response.map { |issue| issue['created_at'] } - - expect_paginated_array_response(size: 3) - expect(response_dates).to eq(response_dates.sort) + expect_paginated_array_response([group_issue.id, group_confidential_issue.id, group_closed_issue.id]) end it 'sorts by updated_at descending when requested' do get api("#{base_url}?order_by=updated_at", user) - response_dates = json_response.map { |issue| issue['updated_at'] } + group_issue.touch(:updated_at) - expect_paginated_array_response(size: 3) - expect(response_dates).to eq(response_dates.sort.reverse) + expect_paginated_array_response([group_issue.id, group_confidential_issue.id, group_closed_issue.id]) end it 'sorts by updated_at ascending when requested' do get api(base_url, user), params: { order_by: :updated_at, sort: :asc } - response_dates = json_response.map { |issue| issue['updated_at'] } - - expect_paginated_array_response(size: 3) - expect(response_dates).to eq(response_dates.sort) + expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue.id]) end end end @@ -691,8 +636,7 @@ describe API::Issues do it 'returns public project issues' do get api("/projects/#{project.id}/issues") - expect_paginated_array_response(size: 2) - expect(json_response.first['title']).to eq(issue.title) + expect_paginated_array_response([issue.id, closed_issue.id]) end end @@ -731,56 +675,49 @@ describe API::Issues do get api("/projects/#{restricted_project.id}/issues", non_member) - expect_paginated_array_response(size: 0) + expect_paginated_array_response([]) end it 'returns project issues without confidential issues for non project members' do get api("#{base_url}/issues", non_member) - expect_paginated_array_response(size: 2) - expect(json_response.first['title']).to eq(issue.title) + expect_paginated_array_response([issue.id, closed_issue.id]) end it 'returns project issues without confidential issues for project members with guest role' do get api("#{base_url}/issues", guest) - expect_paginated_array_response(size: 2) - expect(json_response.first['title']).to eq(issue.title) + expect_paginated_array_response([issue.id, closed_issue.id]) end it 'returns project confidential issues for author' do get api("#{base_url}/issues", author) - expect_paginated_array_response(size: 3) - expect(json_response.first['title']).to eq(issue.title) + expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) end it 'returns project confidential issues for assignee' do get api("#{base_url}/issues", assignee) - expect_paginated_array_response(size: 3) - expect(json_response.first['title']).to eq(issue.title) + expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) end it 'returns project issues with confidential issues for project members' do get api("#{base_url}/issues", user) - expect_paginated_array_response(size: 3) - expect(json_response.first['title']).to eq(issue.title) + expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) end it 'returns project confidential issues for admin' do get api("#{base_url}/issues", admin) - expect_paginated_array_response(size: 3) - expect(json_response.first['title']).to eq(issue.title) + expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) end it 'returns an array of labeled project issues' do get api("#{base_url}/issues", user), params: { labels: label.title } - expect_paginated_array_response(size: 1) - expect(json_response.first['labels']).to eq([label.title]) + expect_paginated_array_response(issue.id) end it 'returns an array of labeled issues when all labels matches' do @@ -792,142 +729,117 @@ describe API::Issues do get api("#{base_url}/issues", user), params: { labels: "#{label.title},#{label_b.title},#{label_c.title}" } - expect_paginated_array_response(size: 1) - expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title]) + expect_paginated_array_response(issue.id) end it 'returns issues matching given search string for title' do get api("#{base_url}/issues?search=#{issue.title}", user) - expect_paginated_array_response(size: 1) - expect(json_response.first['id']).to eq(issue.id) + expect_paginated_array_response(issue.id) end it 'returns issues matching given search string for description' do get api("#{base_url}/issues?search=#{issue.description}", user) - expect_paginated_array_response(size: 1) - expect(json_response.first['id']).to eq(issue.id) + expect_paginated_array_response(issue.id) end it 'returns an array of issues found by iids' do get api("#{base_url}/issues", user), params: { iids: [issue.iid] } - expect_paginated_array_response(size: 1) - expect(json_response.first['id']).to eq(issue.id) + expect_paginated_array_response(issue.id) end it 'returns an empty array if iid does not exist' do get api("#{base_url}/issues", user), params: { iids: [99999] } - expect_paginated_array_response(size: 0) + expect_paginated_array_response([]) end it 'returns an empty array if not all labels matches' do get api("#{base_url}/issues?labels=#{label.title},foo", user) - expect_paginated_array_response(size: 0) + expect_paginated_array_response([]) end it 'returns an array of project issues with any label' do get api("#{base_url}/issues", user), params: { labels: IssuesFinder::FILTER_ANY } - expect_paginated_array_response(size: 1) - expect(json_response.first['id']).to eq(issue.id) + expect_paginated_array_response(issue.id) end it 'returns an array of project issues with no label' do get api("#{base_url}/issues", user), params: { labels: IssuesFinder::FILTER_NONE } - response_ids = json_response.map { |issue| issue['id'] } - - expect_paginated_array_response(size: 2) - expect(response_ids).to contain_exactly(closed_issue.id, confidential_issue.id) + expect_paginated_array_response([confidential_issue.id, closed_issue.id]) end it 'returns an empty array if no project issue matches labels' do get api("#{base_url}/issues", user), params: { labels: 'foo,bar' } - expect_paginated_array_response(size: 0) + expect_paginated_array_response([]) end it 'returns an empty array if no issue matches milestone' do get api("#{base_url}/issues", user), params: { milestone: empty_milestone.title } - expect_paginated_array_response(size: 0) + expect_paginated_array_response([]) end it 'returns an empty array if milestone does not exist' do get api("#{base_url}/issues", user), params: { milestone: :foo } - expect_paginated_array_response(size: 0) + expect_paginated_array_response([]) end it 'returns an array of issues in given milestone' do get api("#{base_url}/issues", user), params: { milestone: milestone.title } - expect_paginated_array_response(size: 2) - expect(json_response.first['id']).to eq(issue.id) - expect(json_response.second['id']).to eq(closed_issue.id) + expect_paginated_array_response([issue.id, closed_issue.id]) end it 'returns an array of issues matching state in milestone' do get api("#{base_url}/issues", user), params: { milestone: milestone.title, state: :closed } - expect_paginated_array_response(size: 1) - expect(json_response.first['id']).to eq(closed_issue.id) + expect_paginated_array_response(closed_issue.id) end it 'returns an array of issues with no milestone' do get api("#{base_url}/issues", user), params: { milestone: no_milestone_title } - expect_paginated_array_response(size: 1) - expect(json_response.first['id']).to eq(confidential_issue.id) + expect_paginated_array_response(confidential_issue.id) end it 'returns an array of issues with any milestone' do get api("#{base_url}/issues", user), params: { milestone: any_milestone_title } - response_ids = json_response.map { |issue| issue['id'] } - - expect_paginated_array_response(size: 2) - expect(response_ids).to contain_exactly(closed_issue.id, issue.id) + expect_paginated_array_response([issue.id, closed_issue.id]) end it 'sorts by created_at descending by default' do get api("#{base_url}/issues", user) - response_dates = json_response.map { |issue| issue['created_at'] } - - expect_paginated_array_response(size: 3) - expect(response_dates).to eq(response_dates.sort.reverse) + expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) end it 'sorts ascending when requested' do get api("#{base_url}/issues", user), params: { sort: :asc } - response_dates = json_response.map { |issue| issue['created_at'] } - - expect_paginated_array_response(size: 3) - expect(response_dates).to eq(response_dates.sort) + expect_paginated_array_response([closed_issue.id, confidential_issue.id, issue.id]) end it 'sorts by updated_at descending when requested' do get api("#{base_url}/issues", user), params: { order_by: :updated_at } - response_dates = json_response.map { |issue| issue['updated_at'] } + issue.touch(:updated_at) - expect_paginated_array_response(size: 3) - expect(response_dates).to eq(response_dates.sort.reverse) + expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) end it 'sorts by updated_at ascending when requested' do get api("#{base_url}/issues", user), params: { order_by: :updated_at, sort: :asc } - response_dates = json_response.map { |issue| issue['updated_at'] } - - expect_paginated_array_response(size: 3) - expect(response_dates).to eq(response_dates.sort) + expect_paginated_array_response([closed_issue.id, confidential_issue.id, issue.id]) end end @@ -1828,21 +1740,21 @@ describe API::Issues do it 'return public project issues' do get api("/projects/#{project.id}/issues/#{issue.iid}/closed_by") - expect_paginated_array_response(size: 1) + expect_paginated_array_response(merge_request.id) end end it 'returns merge requests that will close issue on merge' do get api("/projects/#{project.id}/issues/#{issue.iid}/closed_by", user) - expect_paginated_array_response(size: 1) + expect_paginated_array_response(merge_request.id) end context 'when no merge requests will close issue' do it 'returns empty array' do get api("/projects/#{project.id}/issues/#{closed_issue.iid}/closed_by", user) - expect_paginated_array_response(size: 0) + expect_paginated_array_response([]) end end @@ -1878,7 +1790,7 @@ describe API::Issues do it 'return list of referenced merge requests from issue' do get_related_merge_requests(project.id, issue.iid) - expect_paginated_array_response(size: 1) + expect_paginated_array_response(related_mr.id) end it 'renders 404 if project is not visible' do @@ -1902,15 +1814,14 @@ describe API::Issues do get_related_merge_requests(project.id, issue.iid, user) - expect_paginated_array_response(size: 1) - expect(json_response.first['id']).to eq(related_mr.id) + expect_paginated_array_response(related_mr.id) end context 'no merge request mentioned a issue' do it 'returns empty array' do get_related_merge_requests(project.id, closed_issue.iid, user) - expect_paginated_array_response(size: 0) + expect_paginated_array_response([]) end end @@ -1948,13 +1859,6 @@ describe API::Issues do end end - def expect_paginated_array_response(size: nil) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(size) if size - end - describe 'GET projects/:id/issues/:issue_iid/participants' do it_behaves_like 'issuable participants endpoint' do let(:entity) { issue } diff --git a/spec/requests/api/project_clusters_spec.rb b/spec/requests/api/project_clusters_spec.rb index e34164aa66a..9bab1f95150 100644 --- a/spec/requests/api/project_clusters_spec.rb +++ b/spec/requests/api/project_clusters_spec.rb @@ -266,6 +266,23 @@ describe API::ProjectClusters do end end end + + context 'when user tries to add multiple clusters' do + before do + create(:cluster, :provided_by_gcp, :project, + projects: [project]) + + post api("/projects/#{project.id}/clusters/user", current_user), params: cluster_params + end + + it 'should respond with 403' do + expect(response).to have_gitlab_http_status(403) + end + + it 'should return an appropriate message' do + expect(json_response['message']).to include('Instance does not support multiple Kubernetes clusters') + end + end end describe 'PUT /projects/:id/clusters/:cluster_id' do diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index ffe4512fa6f..a01b494f615 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -948,6 +948,7 @@ describe API::Projects do expect(json_response['shared_with_groups'].length).to eq(1) expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id) expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name) + expect(json_response['shared_with_groups'][0]['group_full_path']).to eq(group.full_path) expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access) expect(json_response['shared_with_groups'][0]['expires_at']).to be_nil expect(json_response['only_allow_merge_if_pipeline_succeeds']).to eq(project.only_allow_merge_if_pipeline_succeeds) @@ -967,6 +968,7 @@ describe API::Projects do expect(json_response['shared_with_groups'].length).to eq(1) expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id) expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name) + expect(json_response['shared_with_groups'][0]['group_full_path']).to eq(group.full_path) expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access) expect(json_response['shared_with_groups'][0]['expires_at']).to eq(expires_at.to_s) end diff --git a/spec/requests/api/release/links_spec.rb b/spec/requests/api/release/links_spec.rb index 9d62257d470..ba948e37e2f 100644 --- a/spec/requests/api/release/links_spec.rb +++ b/spec/requests/api/release/links_spec.rb @@ -74,16 +74,6 @@ describe API::Release::Links do end end end - - context 'when feature flag is disabled' do - before do - stub_feature_flags(releases_page: false) - end - - it_behaves_like '404 response' do - let(:request) { get api("/projects/#{project.id}/releases/v0.1/assets/links", maintainer) } - end - end end describe 'GET /projects/:id/releases/:tag_name/assets/links/:link_id' do @@ -129,16 +119,6 @@ describe API::Release::Links do end end end - - context 'when feature flag is disabled' do - before do - stub_feature_flags(releases_page: false) - end - - it_behaves_like '404 response' do - let(:request) { get api("/projects/#{project.id}/releases/non_existing_tag/assets/links/#{release_link.id}", maintainer) } - end - end end describe 'POST /projects/:id/releases/:tag_name/assets/links' do @@ -231,19 +211,6 @@ describe API::Release::Links do end end end - - context 'when feature flag is disabled' do - before do - stub_feature_flags(releases_page: false) - end - - it_behaves_like '404 response' do - let(:request) do - post api("/projects/#{project.id}/releases/v0.1/assets/links", maintainer), - params: params - end - end - end end describe 'PUT /projects/:id/releases/:tag_name/assets/links/:link_id' do @@ -328,19 +295,6 @@ describe API::Release::Links do end end end - - context 'when feature flag is disabled' do - before do - stub_feature_flags(releases_page: false) - end - - it_behaves_like '404 response' do - let(:request) do - put api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer), - params: params - end - end - end end describe 'DELETE /projects/:id/releases/:tag_name/assets/links/:link_id' do @@ -401,17 +355,5 @@ describe API::Release::Links do end end end - - context 'when feature flag is disabled' do - before do - stub_feature_flags(releases_page: false) - end - - it_behaves_like '404 response' do - let(:request) do - delete api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer) - end - end - end end end diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb index 978fa0142c2..811e23fb854 100644 --- a/spec/requests/api/releases_spec.rb +++ b/spec/requests/api/releases_spec.rb @@ -83,18 +83,6 @@ describe API::Releases do end end end - - context 'when feature flag is disabled' do - before do - stub_feature_flags(releases_page: false) - end - - it 'cannot find the API' do - get api("/projects/#{project.id}/releases", maintainer) - - expect(response).to have_gitlab_http_status(:not_found) - end - end end describe 'GET /projects/:id/releases/:tag_name' do @@ -205,18 +193,6 @@ describe API::Releases do end end end - - context 'when feature flag is disabled' do - before do - stub_feature_flags(releases_page: false) - end - - it 'cannot find the API' do - get api("/projects/#{project.id}/releases/v0.1", maintainer) - - expect(response).to have_gitlab_http_status(:not_found) - end - end end describe 'POST /projects/:id/releases' do @@ -458,18 +434,6 @@ describe API::Releases do expect(response).to have_gitlab_http_status(:conflict) end end - - context 'when feature flag is disabled' do - before do - stub_feature_flags(releases_page: false) - end - - it 'cannot find the API' do - post api("/projects/#{project.id}/releases", maintainer), params: params - - expect(response).to have_gitlab_http_status(:not_found) - end - end end describe 'PUT /projects/:id/releases/:tag_name' do @@ -565,19 +529,6 @@ describe API::Releases do end end end - - context 'when feature flag is disabled' do - before do - stub_feature_flags(releases_page: false) - end - - it 'cannot find the API' do - put api("/projects/#{project.id}/releases/v0.1", non_project_member), - params: params - - expect(response).to have_gitlab_http_status(:not_found) - end - end end describe 'DELETE /projects/:id/releases/:tag_name' do @@ -648,17 +599,5 @@ describe API::Releases do end end end - - context 'when feature flag is disabled' do - before do - stub_feature_flags(releases_page: false) - end - - it 'cannot find the API' do - delete api("/projects/#{project.id}/releases/v0.1", non_project_member) - - expect(response).to have_gitlab_http_status(:not_found) - end - end end end diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index cfbda63bb30..45fb1562e84 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -63,7 +63,8 @@ describe API::Settings, 'Settings' do terms: 'Hello world!', performance_bar_allowed_group_path: group.full_path, instance_statistics_visibility_private: true, - diff_max_patch_bytes: 150_000 + diff_max_patch_bytes: 150_000, + default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE } expect(response).to have_gitlab_http_status(200) @@ -88,6 +89,7 @@ describe API::Settings, 'Settings' do expect(json_response['performance_bar_allowed_group_id']).to eq(group.id) expect(json_response['instance_statistics_visibility_private']).to be(true) expect(json_response['diff_max_patch_bytes']).to eq(150_000) + expect(json_response['default_branch_protection']).to eq(Gitlab::Access::PROTECTION_DEV_CAN_MERGE) end end diff --git a/spec/requests/api/wikis_spec.rb b/spec/requests/api/wikis_spec.rb index f5092e8e2b5..6109829aad1 100644 --- a/spec/requests/api/wikis_spec.rb +++ b/spec/requests/api/wikis_spec.rb @@ -22,7 +22,7 @@ describe API::Wikis do context 'when wiki has pages' do let!(:pages) do [create(:wiki_page, wiki: project_wiki, attrs: { title: 'page1', content: 'content of page1' }), - create(:wiki_page, wiki: project_wiki, attrs: { title: 'page2', content: 'content of page2' })] + create(:wiki_page, wiki: project_wiki, attrs: { title: 'page2.with.dot', content: 'content of page2' })] end it 'returns the list of wiki pages without content' do diff --git a/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb index 08ffc3c3a53..0ff777388e5 100644 --- a/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb +++ b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb @@ -19,6 +19,41 @@ describe RuboCop::Cop::InjectEnterpriseEditionModule do SOURCE end + it 'does not flag the use of `prepend EEFoo` in the middle of a file' do + expect_no_offenses(<<~SOURCE) + class Foo + prepend EEFoo + end + SOURCE + end + + it 'flags the use of `prepend EE::Foo::Bar` in the middle of a file' do + expect_offense(<<~SOURCE) + class Foo + prepend EE::Foo::Bar + ^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions + end + SOURCE + end + + it 'flags the use of `prepend(EE::Foo::Bar)` in the middle of a file' do + expect_offense(<<~SOURCE) + class Foo + prepend(EE::Foo::Bar) + ^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions + end + SOURCE + end + + it 'flags the use of `prepend EE::Foo::Bar::Baz` in the middle of a file' do + expect_offense(<<~SOURCE) + class Foo + prepend EE::Foo::Bar::Baz + ^^^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions + end + SOURCE + end + it 'flags the use of `prepend ::EE` in the middle of a file' do expect_offense(<<~SOURCE) class Foo diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb index 7ce7d2d882a..6674d89518e 100644 --- a/spec/services/ci/process_pipeline_service_spec.rb +++ b/spec/services/ci/process_pipeline_service_spec.rb @@ -762,7 +762,7 @@ describe Ci::ProcessPipelineService, '#execute' do end def manual_actions - pipeline.manual_actions(true) + pipeline.manual_actions.reload end def create_build(name, **opts) diff --git a/spec/services/projects/protect_default_branch_service_spec.rb b/spec/services/projects/protect_default_branch_service_spec.rb new file mode 100644 index 00000000000..c145b2c06c6 --- /dev/null +++ b/spec/services/projects/protect_default_branch_service_spec.rb @@ -0,0 +1,242 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Projects::ProtectDefaultBranchService do + let(:service) { described_class.new(project) } + let(:project) { instance_spy(Project) } + + describe '#execute' do + before do + allow(service) + .to receive(:protect_default_branch) + end + + context 'without a default branch' do + it 'does nothing' do + allow(service) + .to receive(:default_branch) + .and_return(nil) + + service.execute + + expect(service) + .not_to have_received(:protect_default_branch) + end + end + + context 'with a default branch' do + it 'protects the default branch' do + allow(service) + .to receive(:default_branch) + .and_return('master') + + service.execute + + expect(service) + .to have_received(:protect_default_branch) + end + end + end + + describe '#protect_default_branch' do + before do + allow(service) + .to receive(:default_branch) + .and_return('master') + + allow(project) + .to receive(:change_head) + .with('master') + + allow(service) + .to receive(:create_protected_branch) + end + + context 'when branch protection is needed' do + before do + allow(service) + .to receive(:protect_branch?) + .and_return(true) + + allow(service) + .to receive(:create_protected_branch) + end + + it 'changes the HEAD of the project' do + service.protect_default_branch + + expect(project) + .to have_received(:change_head) + end + + it 'protects the default branch' do + service.protect_default_branch + + expect(service) + .to have_received(:create_protected_branch) + end + end + + context 'when branch protection is not needed' do + before do + allow(service) + .to receive(:protect_branch?) + .and_return(false) + end + + it 'changes the HEAD of the project' do + service.protect_default_branch + + expect(project) + .to have_received(:change_head) + end + + it 'does not protect the default branch' do + service.protect_default_branch + + expect(service) + .not_to have_received(:create_protected_branch) + end + end + end + + describe '#create_protected_branch' do + it 'creates the protected branch' do + creator = instance_spy(User) + create_service = instance_spy(ProtectedBranches::CreateService) + access_level = Gitlab::Access::DEVELOPER + params = { + name: 'master', + push_access_levels_attributes: [{ access_level: access_level }], + merge_access_levels_attributes: [{ access_level: access_level }] + } + + allow(project) + .to receive(:creator) + .and_return(creator) + + allow(ProtectedBranches::CreateService) + .to receive(:new) + .with(project, creator, params) + .and_return(create_service) + + allow(service) + .to receive(:push_access_level) + .and_return(access_level) + + allow(service) + .to receive(:merge_access_level) + .and_return(access_level) + + allow(service) + .to receive(:default_branch) + .and_return('master') + + allow(create_service) + .to receive(:execute) + .with(skip_authorization: true) + + service.create_protected_branch + + expect(create_service) + .to have_received(:execute) + end + end + + describe '#protect_branch?' do + context 'when default branch protection is disabled' do + it 'returns false' do + allow(Gitlab::CurrentSettings) + .to receive(:default_branch_protection) + .and_return(Gitlab::Access::PROTECTION_NONE) + + expect(service.protect_branch?).to eq(false) + end + end + + context 'when default branch protection is enabled' do + before do + allow(Gitlab::CurrentSettings) + .to receive(:default_branch_protection) + .and_return(Gitlab::Access::PROTECTION_DEV_CAN_MERGE) + + allow(service) + .to receive(:default_branch) + .and_return('master') + end + + it 'returns false if the branch is already protected' do + allow(ProtectedBranch) + .to receive(:protected?) + .with(project, 'master') + .and_return(true) + + expect(service.protect_branch?).to eq(false) + end + + it 'returns true if the branch is not yet protected' do + allow(ProtectedBranch) + .to receive(:protected?) + .with(project, 'master') + .and_return(false) + + expect(service.protect_branch?).to eq(true) + end + end + end + + describe '#default_branch' do + it 'returns the default branch of the project' do + allow(project) + .to receive(:default_branch) + .and_return('master') + + expect(service.default_branch).to eq('master') + end + end + + describe '#push_access_level' do + context 'when developers can push' do + it 'returns the DEVELOPER access level' do + allow(Gitlab::CurrentSettings) + .to receive(:default_branch_protection) + .and_return(Gitlab::Access::PROTECTION_DEV_CAN_PUSH) + + expect(service.push_access_level).to eq(Gitlab::Access::DEVELOPER) + end + end + + context 'when developers can not push' do + it 'returns the MAINTAINER access level' do + allow(Gitlab::CurrentSettings) + .to receive(:default_branch_protection) + .and_return(Gitlab::Access::PROTECTION_DEV_CAN_MERGE) + + expect(service.push_access_level).to eq(Gitlab::Access::MAINTAINER) + end + end + end + + describe '#merge_access_level' do + context 'when developers can merge' do + it 'returns the DEVELOPER access level' do + allow(Gitlab::CurrentSettings) + .to receive(:default_branch_protection) + .and_return(Gitlab::Access::PROTECTION_DEV_CAN_MERGE) + + expect(service.merge_access_level).to eq(Gitlab::Access::DEVELOPER) + end + end + + context 'when developers can not merge' do + it 'returns the MAINTAINER access level' do + allow(Gitlab::CurrentSettings) + .to receive(:default_branch_protection) + .and_return(Gitlab::Access::PROTECTION_DEV_CAN_PUSH) + + expect(service.merge_access_level).to eq(Gitlab::Access::MAINTAINER) + end + end + end +end diff --git a/spec/services/suggestions/apply_service_spec.rb b/spec/services/suggestions/apply_service_spec.rb index 3a483717756..e5ca1c155ed 100644 --- a/spec/services/suggestions/apply_service_spec.rb +++ b/spec/services/suggestions/apply_service_spec.rb @@ -117,13 +117,184 @@ describe Suggestions::ApplyService do expect(commit.committer_email).to eq(user.commit_email) expect(commit.author_name).to eq(user.name) end + + context 'when it fails to apply because the file was changed' do + it 'returns error message' do + service = instance_double(Files::UpdateService) + + expect(Files::UpdateService).to receive(:new) + .and_return(service) + + allow(service).to receive(:execute) + .and_raise(Files::UpdateService::FileChangedError) + + result = subject.execute(suggestion) + + expect(result).to eq(message: 'The file has been changed', status: :error) + end + end + + context 'when diff ref from position is different from repo diff refs' do + it 'returns error message' do + outdated_refs = Gitlab::Diff::DiffRefs.new(base_sha: 'foo', start_sha: 'bar', head_sha: 'outdated') + + allow(suggestion).to receive(:appliable?) { true } + allow(suggestion.position).to receive(:diff_refs) { outdated_refs } + + result = subject.execute(suggestion) + + expect(result).to eq(message: 'The file has been changed', status: :error) + end + end + + context 'multiple suggestions applied' do + let(:expected_content) do + <<-CONTENT.strip_heredoc + require 'fileutils' + require 'open3' + + module Popen + extend self + + + def popen(cmd, path=nil) + unless cmd.is_a?(Array) + # v1 change + end + + path ||= Dir.pwd + # v1 change + vars = { + "PWD" => path + } + + options = { + chdir: path + } + # v2 change + unless File.directory?(path) + FileUtils.mkdir_p(path) + end + + @cmd_output = "" + # v2 change + + Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| + @cmd_output << stdout.read + @cmd_output << stderr.read + @cmd_status = wait_thr.value.exitstatus + end + + return @cmd_output, @cmd_status + end + end + CONTENT + end + + let(:merge_request) do + create(:merge_request, source_project: project, + target_project: project) + end + + def create_suggestion(diff, old_line: nil, new_line: nil, from_content:, to_content:, path:) + position = Gitlab::Diff::Position.new(old_path: path, + new_path: path, + old_line: old_line, + new_line: new_line, + diff_refs: diff.diff_refs) + + suggestion_note = create(:diff_note_on_merge_request, noteable: merge_request, + original_position: position, + position: position, + project: project) + create(:suggestion, note: suggestion_note, + from_content: from_content, + to_content: to_content) + end + + def apply_suggestion(suggestion) + suggestion.note.reload + merge_request.reload + merge_request.clear_memoized_shas + + result = subject.execute(suggestion) + refresh = MergeRequests::RefreshService.new(project, user) + refresh.execute(merge_request.diff_head_sha, + suggestion.commit_id, + merge_request.source_branch_ref) + + result + end + + def fetch_raw_diff(suggestion) + project.reload.commit(suggestion.commit_id).diffs.diff_files.first.diff.diff + end + + it 'applies multiple suggestions in subsequent versions correctly' do + diff = merge_request.merge_request_diff + path = 'files/ruby/popen.rb' + + suggestion_1_changes = { old_line: nil, + new_line: 13, + from_content: "\n", + to_content: "# v1 change\n", + path: path } + + suggestion_2_changes = { old_line: 24, + new_line: 31, + from_content: " @cmd_output << stderr.read\n", + to_content: "# v2 change\n", + path: path } + + suggestion_1 = create_suggestion(diff, suggestion_1_changes) + suggestion_2 = create_suggestion(diff, suggestion_2_changes) + + apply_suggestion(suggestion_1) + + suggestion_1_diff = fetch_raw_diff(suggestion_1) + + # rubocop: disable Layout/TrailingWhitespace + expected_suggestion_1_diff = <<-CONTENT.strip_heredoc + @@ -10,7 +10,7 @@ module Popen + end + + path ||= Dir.pwd + - + +# v1 change + vars = { + "PWD" => path + } + CONTENT + # rubocop: enable Layout/TrailingWhitespace + + apply_suggestion(suggestion_2) + + suggestion_2_diff = fetch_raw_diff(suggestion_2) + + # rubocop: disable Layout/TrailingWhitespace + expected_suggestion_2_diff = <<-CONTENT.strip_heredoc + @@ -28,7 +28,7 @@ module Popen + + Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| + @cmd_output << stdout.read + - @cmd_output << stderr.read + +# v2 change + @cmd_status = wait_thr.value.exitstatus + end + CONTENT + # rubocop: enable Layout/TrailingWhitespace + + expect(suggestion_1_diff.strip).to eq(expected_suggestion_1_diff.strip) + expect(suggestion_2_diff.strip).to eq(expected_suggestion_2_diff.strip) + end + end end context 'fork-project' do let(:project) { create(:project, :public, :repository) } let(:forked_project) do - fork_project_with_submodules(project, user) + fork_project_with_submodules(project, user, repository: project.repository) end let(:merge_request) do diff --git a/spec/sidekiq/cron/job_gem_dependency_spec.rb b/spec/sidekiq/cron/job_gem_dependency_spec.rb index 2e30cf025b0..2e7de75fd08 100644 --- a/spec/sidekiq/cron/job_gem_dependency_spec.rb +++ b/spec/sidekiq/cron/job_gem_dependency_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Sidekiq::Cron::Job do describe 'cron jobs' do - context 'when rufus-scheduler depends on ZoTime or EoTime' do + context 'when Fugit depends on ZoTime or EoTime' do before do described_class .create(name: 'TestCronWorker', @@ -10,7 +10,7 @@ describe Sidekiq::Cron::Job do class: Settings.cron_jobs[:pipeline_schedule_worker]['job_class']) end - it 'does not get "Rufus::Scheduler::ZoTime/EtOrbi::EoTime into an exact number"' do + it 'does not get any errors' do expect { described_class.all.first.should_enque?(Time.now) }.not_to raise_error end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 89357056c93..72684caad32 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -22,7 +22,7 @@ if rspec_profiling_is_configured && (!ENV.key?('CI') || branch_can_be_profiled) require 'rspec_profiling/rspec' end -if ENV['CI'] && !ENV['NO_KNAPSACK'] +if ENV['CI'] && ENV['KNAPSACK_GENERATE_REPORT'] && !ENV['NO_KNAPSACK'] require 'knapsack' Knapsack::Adapters::RSpecAdapter.bind end @@ -127,6 +127,16 @@ RSpec.configure do |config| .and_return(false) end + config.before(:suite) do + # Set latest release blog post URL for "What's new?" link + Gitlab::ReleaseBlogPost.instance.instance_variable_set(:@url, 'https://about.gitlab.com') + end + + config.before(:example, :quarantine) do + # Skip tests in quarantine unless we explicitly focus on them. + skip('In quarantine') unless config.inclusion_filter[:quarantine] + end + config.before(:example, :request_store) do RequestStore.begin! end diff --git a/spec/support/helpers/api_helpers.rb b/spec/support/helpers/api_helpers.rb index a57a3b2cf34..4a9ce9beb78 100644 --- a/spec/support/helpers/api_helpers.rb +++ b/spec/support/helpers/api_helpers.rb @@ -36,4 +36,11 @@ module ApiHelpers full_path end + + def expect_paginated_array_response(items) + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.map { |item| item['id'] }).to eq(Array(items)) + end end diff --git a/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb b/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb index 1f688c0f9d3..dcf7c1a90c2 100644 --- a/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb @@ -32,11 +32,13 @@ shared_examples 'backfill migration for project repositories' do |storage| it 'inserts rows in a single query' do projects.create!(name: 'foo', path: 'foo', namespace_id: group.id, storage_version: storage_version, repository_storage: shard.name) + group2 = namespaces.create!(name: 'gro', path: 'gro') control_count = ActiveRecord::QueryRecorder.new { described_class.new.perform(1, projects.last.id) } projects.create!(name: 'bar', path: 'bar', namespace_id: group.id, storage_version: storage_version, repository_storage: shard.name) - projects.create!(name: 'zoo', path: 'zoo', namespace_id: group.id, storage_version: storage_version, repository_storage: shard.name) + projects.create!(name: 'top', path: 'top', namespace_id: group.id, storage_version: storage_version, repository_storage: shard.name) + projects.create!(name: 'zoo', path: 'zoo', namespace_id: group2.id, storage_version: storage_version, repository_storage: shard.name) expect { described_class.new.perform(1, projects.last.id) }.not_to exceed_query_limit(control_count) end diff --git a/spec/views/help/instance_configuration.html.haml_spec.rb b/spec/views/help/instance_configuration.html.haml_spec.rb index f30b5881fde..ceb7e34a540 100644 --- a/spec/views/help/instance_configuration.html.haml_spec.rb +++ b/spec/views/help/instance_configuration.html.haml_spec.rb @@ -13,9 +13,9 @@ describe 'help/instance_configuration' do it 'has links to several sections' do render - expect(rendered).to have_link(nil, '#ssh-host-keys-fingerprints') if ssh_settings.any? - expect(rendered).to have_link(nil, '#gitlab-pages') - expect(rendered).to have_link(nil, '#gitlab-ci') + expect(rendered).to have_link(nil, href: '#ssh-host-keys-fingerprints') if ssh_settings.any? + expect(rendered).to have_link(nil, href: '#gitlab-pages') + expect(rendered).to have_link(nil, href: '#gitlab-ci') end it 'has several sections' do diff --git a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb index ec20c346234..2852aa380b2 100644 --- a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb +++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb @@ -51,28 +51,10 @@ describe 'layouts/nav/sidebar/_project' do end describe 'releases entry' do - describe 'when releases feature flag is disabled' do - before do - stub_feature_flags(releases_page: false) - end - - it 'does not render releases link' do - render - - expect(rendered).not_to have_link('Releases', href: project_releases_path(project)) - end - end - - describe 'when releases feature flags is enabled' do - before do - stub_feature_flags(releases_page: true) - end - - it 'renders releases link' do - render + it 'renders releases link' do + render - expect(rendered).to have_link('Releases', href: project_releases_path(project)) - end + expect(rendered).to have_link('Releases', href: project_releases_path(project)) end end end diff --git a/spec/views/projects/commit/show.html.haml_spec.rb b/spec/views/projects/commit/show.html.haml_spec.rb index a9c32122600..d07099489e5 100644 --- a/spec/views/projects/commit/show.html.haml_spec.rb +++ b/spec/views/projects/commit/show.html.haml_spec.rb @@ -54,9 +54,9 @@ describe 'projects/commit/show.html.haml' do end it 'shows that it is in the context of a merge request' do - merge_request_url = diffs_project_merge_request_url(project, merge_request, commit_id: commit.id) + merge_request_url = diffs_project_merge_request_path(project, merge_request, commit_id: commit.id) expect(rendered).to have_content("This commit is part of merge request") - expect(rendered).to have_link(merge_request.to_reference, merge_request_url) + expect(rendered).to have_link(merge_request.to_reference, href: merge_request_url) end end end diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb index a159f24f876..4895a968d6e 100644 --- a/spec/workers/git_garbage_collect_worker_spec.rb +++ b/spec/workers/git_garbage_collect_worker_spec.rb @@ -71,6 +71,17 @@ describe GitGarbageCollectWorker do subject.perform(project.id) end + + context 'when the repository has joined a pool' do + let!(:pool) { create(:pool_repository, :ready) } + let(:project) { pool.source_project } + + it 'ensures the repositories are linked' do + expect_any_instance_of(PoolRepository).to receive(:link_repository).once + + subject.perform(project.id) + end + end end context 'when no lease can be obtained' do diff --git a/spec/workers/remote_mirror_notification_worker_spec.rb b/spec/workers/remote_mirror_notification_worker_spec.rb new file mode 100644 index 00000000000..e3db10ed645 --- /dev/null +++ b/spec/workers/remote_mirror_notification_worker_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +describe RemoteMirrorNotificationWorker, :mailer do + set(:project) { create(:project, :repository, :remote_mirror) } + set(:mirror) { project.remote_mirrors.first } + + describe '#execute' do + it 'calls NotificationService#remote_mirror_update_failed when the mirror exists' do + mirror.update_column(:last_error, "There was a problem fetching") + + expect(NotificationService).to receive_message_chain(:new, :remote_mirror_update_failed) + + subject.perform(mirror.id) + + expect(mirror.reload.error_notification_sent?).to be_truthy + end + + it 'does nothing when the mirror has no errors' do + expect(NotificationService).not_to receive(:new) + + subject.perform(mirror.id) + end + + it 'does nothing when the mirror does not exist' do + expect(NotificationService).not_to receive(:new) + + subject.perform(RemoteMirror.maximum(:id).to_i.succ) + end + + it 'does nothing when a notification has already been sent' do + mirror.update_columns(last_error: "There was a problem fetching", + error_notification_sent: true) + + expect(NotificationService).not_to receive(:new) + + subject.perform(mirror.id) + end + end +end diff --git a/spec/workers/repository_update_remote_mirror_worker_spec.rb b/spec/workers/repository_update_remote_mirror_worker_spec.rb index d73b0b53713..b582a3650b6 100644 --- a/spec/workers/repository_update_remote_mirror_worker_spec.rb +++ b/spec/workers/repository_update_remote_mirror_worker_spec.rb @@ -22,6 +22,13 @@ describe RepositoryUpdateRemoteMirrorWorker do expect { subject.perform(remote_mirror.id, Time.now) }.to change { remote_mirror.reload.update_status }.to('finished') end + it 'resets the notification flag upon success' do + expect_any_instance_of(Projects::UpdateRemoteMirrorService).to receive(:execute).with(remote_mirror).and_return(status: :success) + remote_mirror.update_column(:error_notification_sent, true) + + expect { subject.perform(remote_mirror.id, Time.now) }.to change { remote_mirror.reload.error_notification_sent }.to(false) + end + it 'sets status as failed when update remote mirror service executes with errors' do error_message = 'fail!' |