diff options
Diffstat (limited to 'spec')
43 files changed, 578 insertions, 154 deletions
diff --git a/spec/controllers/projects/merge_requests/content_controller_spec.rb b/spec/controllers/projects/merge_requests/content_controller_spec.rb new file mode 100644 index 00000000000..2879e06aee4 --- /dev/null +++ b/spec/controllers/projects/merge_requests/content_controller_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Projects::MergeRequests::ContentController do + let(:project) { create(:project, :repository) } + let(:user) { create(:user) } + let(:merge_request) { create(:merge_request, target_project: project, source_project: project) } + + before do + sign_in(user) + end + + def do_request + get :widget, params: { + namespace_id: project.namespace.to_param, + project_id: project, + id: merge_request.iid, + format: :json + } + end + + describe 'GET widget' do + context 'user has access to the project' do + before do + expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original + + project.add_maintainer(user) + end + + it 'renders widget MR entity as json' do + do_request + + expect(response).to match_response_schema('entities/merge_request_widget') + end + + it 'checks whether the MR can be merged' do + controller.instance_variable_set(:@merge_request, merge_request) + + expect(merge_request).to receive(:check_mergeability) + + do_request + end + + it 'closes an MR with moved source project' do + merge_request.update_column(:source_project_id, nil) + + expect { do_request }.to change { merge_request.reload.open? }.from(true).to(false) + end + end + + context 'user does not have access to the project' do + it 'renders widget MR entity as json' do + do_request + + expect(response).to have_http_status(:not_found) + end + end + end +end diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb index 5c7f8d95f82..68eabce8513 100644 --- a/spec/controllers/projects/services_controller_spec.rb +++ b/spec/controllers/projects/services_controller_spec.rb @@ -128,7 +128,7 @@ describe Projects::ServicesController do params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: { active: true } } expect(response).to redirect_to(project_settings_integrations_path(project)) - expect(flash[:notice]).to eq 'JIRA activated.' + expect(flash[:notice]).to eq 'Jira activated.' end end @@ -137,17 +137,17 @@ describe Projects::ServicesController do put :update, params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: { active: false } } - expect(flash[:notice]).to eq 'JIRA settings saved, but not activated.' + expect(flash[:notice]).to eq 'Jira settings saved, but not activated.' end end - context 'when activating JIRA service from a template' do + context 'when activating Jira service from a template' do let(:template_service) { create(:jira_service, project: project, template: true) } - it 'activate JIRA service from template' do + it 'activate Jira service from template' do put :update, params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: { active: true } } - expect(flash[:notice]).to eq 'JIRA activated.' + expect(flash[:notice]).to eq 'Jira activated.' end end end diff --git a/spec/factories/deployments.rb b/spec/factories/deployments.rb index db438ad32d3..1c7787bc1a6 100644 --- a/spec/factories/deployments.rb +++ b/spec/factories/deployments.rb @@ -22,6 +22,10 @@ FactoryBot.define do ref 'pages-deploy' end + trait :on_cluster do + cluster factory: %i(cluster provided_by_gcp) + end + trait :running do status :running end diff --git a/spec/features/projects/files/user_reads_pipeline_status_spec.rb b/spec/features/projects/files/user_reads_pipeline_status_spec.rb index ff0aa933a3e..5bce96d9b80 100644 --- a/spec/features/projects/files/user_reads_pipeline_status_spec.rb +++ b/spec/features/projects/files/user_reads_pipeline_status_spec.rb @@ -7,6 +7,8 @@ describe 'user reads pipeline status', :js do let(:x110_pipeline) { create_pipeline('x1.1.0', 'failed') } before do + stub_feature_flags(vue_file_list: false) + project.add_maintainer(user) project.repository.add_tag(user, 'x1.1.0', 'v1.1.0') diff --git a/spec/features/projects/services/user_activates_jira_spec.rb b/spec/features/projects/services/user_activates_jira_spec.rb index 08e1855d034..c52f38e2806 100644 --- a/spec/features/projects/services/user_activates_jira_spec.rb +++ b/spec/features/projects/services/user_activates_jira_spec.rb @@ -29,27 +29,27 @@ describe 'User activates Jira', :js do server_info = { key: 'value' }.to_json WebMock.stub_request(:get, test_url).with(basic_auth: %w(username password)).to_return(body: server_info) - click_link('JIRA') + click_link('Jira') fill_form click_button('Test settings and save changes') wait_for_requests end - it 'activates the JIRA service' do - expect(page).to have_content('JIRA activated.') + it 'activates the Jira service' do + expect(page).to have_content('Jira activated.') expect(current_path).to eq(project_settings_integrations_path(project)) end - it 'shows the JIRA link in the menu' do + it 'shows the Jira link in the menu' do page.within('.nav-sidebar') do - expect(page).to have_link('JIRA', href: url) + expect(page).to have_link('Jira', href: url) end end end context 'when Jira connection test fails' do it 'shows errors when some required fields are not filled in' do - click_link('JIRA') + click_link('Jira') check 'Active' fill_in 'service_password', with: 'password' @@ -60,11 +60,11 @@ describe 'User activates Jira', :js do end end - it 'activates the JIRA service' do + it 'activates the Jira service' do WebMock.stub_request(:get, test_url).with(basic_auth: %w(username password)) .to_raise(JIRA::HTTPError.new(double(message: 'message'))) - click_link('JIRA') + click_link('Jira') fill_form click_button('Test settings and save changes') wait_for_requests @@ -75,7 +75,7 @@ describe 'User activates Jira', :js do find('.flash-alert .flash-action').click wait_for_requests - expect(page).to have_content('JIRA activated.') + expect(page).to have_content('Jira activated.') expect(current_path).to eq(project_settings_integrations_path(project)) end end @@ -83,19 +83,19 @@ describe 'User activates Jira', :js do describe 'user sets Jira Service but keeps it disabled' do before do - click_link('JIRA') + click_link('Jira') fill_form(false) click_button('Save changes') end - it 'saves but does not activate the JIRA service' do - expect(page).to have_content('JIRA settings saved, but not activated.') + it 'saves but does not activate the Jira service' do + expect(page).to have_content('Jira settings saved, but not activated.') expect(current_path).to eq(project_settings_integrations_path(project)) end - it 'does not show the JIRA link in the menu' do + it 'does not show the Jira link in the menu' do page.within('.nav-sidebar') do - expect(page).not_to have_link('JIRA', href: url) + expect(page).not_to have_link('Jira', href: url) end end end diff --git a/spec/features/projects/show/user_sees_last_commit_ci_status_spec.rb b/spec/features/projects/show/user_sees_last_commit_ci_status_spec.rb index e277bfb8011..89ce4b50781 100644 --- a/spec/features/projects/show/user_sees_last_commit_ci_status_spec.rb +++ b/spec/features/projects/show/user_sees_last_commit_ci_status_spec.rb @@ -3,6 +3,10 @@ require 'spec_helper' describe 'Projects > Show > User sees last commit CI status' do set(:project) { create(:project, :repository, :public) } + before do + stub_feature_flags(vue_file_list: false) + end + it 'shows the project README', :js do project.enable_ci pipeline = create(:ci_pipeline, project: project, sha: project.commit.sha, ref: 'master') diff --git a/spec/fixtures/api/schemas/entities/merge_request_widget.json b/spec/fixtures/api/schemas/entities/merge_request_widget.json index 7018cb9a305..eac1dbc6474 100644 --- a/spec/fixtures/api/schemas/entities/merge_request_widget.json +++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json @@ -99,7 +99,8 @@ "revert_in_fork_path": { "type": ["string", "null"] }, "email_patches_path": { "type": "string" }, "plain_diff_path": { "type": "string" }, - "status_path": { "type": "string" }, + "merge_request_basic_path": { "type": "string" }, + "merge_request_widget_path": { "type": "string" }, "new_blob_path": { "type": ["string", "null"] }, "merge_check_path": { "type": "string" }, "ci_environments_status_path": { "type": "string" }, diff --git a/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap b/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap index 3ad6bfa9e5f..cd8372a8800 100644 --- a/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap +++ b/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap @@ -27,8 +27,8 @@ exports[`Repository last commit component renders commit widget 1`] = ` href="https://test.com/commit/123" > - Commit title - + Commit title + </gllink-stub> <!----> @@ -41,12 +41,12 @@ exports[`Repository last commit component renders commit widget 1`] = ` href="https://test.com/test" > - Test - + Test + </gllink-stub> - authored - + authored + <timeagotooltip-stub cssclass="" time="2019-01-01" @@ -81,8 +81,8 @@ exports[`Repository last commit component renders commit widget 1`] = ` class="label label-monospace monospace" > - 12345678 - + 12345678 + </div> <clipboardbutton-stub diff --git a/spec/frontend/repository/components/last_commit_spec.js b/spec/frontend/repository/components/last_commit_spec.js index 972690a60f6..14479f3c3a4 100644 --- a/spec/frontend/repository/components/last_commit_spec.js +++ b/spec/frontend/repository/components/last_commit_spec.js @@ -1,4 +1,5 @@ import { shallowMount } from '@vue/test-utils'; +import { GlLoadingIcon } from '@gitlab/ui'; import LastCommit from '~/repository/components/last_commit.vue'; import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; @@ -6,7 +7,7 @@ let vm; function createCommitData(data = {}) { return { - id: '123456789', + sha: '123456789', title: 'Commit title', message: 'Commit message', webUrl: 'https://test.com/commit/123', @@ -16,7 +17,7 @@ function createCommitData(data = {}) { avatarUrl: 'https://test.com', webUrl: 'https://test.com/test', }, - pipeline: { + latestPipeline: { detailedStatus: { detailsPath: 'https://test.com/pipeline', icon: 'failed', @@ -52,12 +53,12 @@ describe('Repository last commit component', () => { it.each` loading | label - ${true} | ${'hides'} - ${false} | ${'shows'} - `('$label when $loading is true', ({ loading }) => { + ${true} | ${'shows'} + ${false} | ${'hides'} + `('$label when loading icon $loading is true', ({ loading }) => { factory(createCommitData(), loading); - expect(vm.isEmpty()).toBe(loading); + expect(vm.find(GlLoadingIcon).exists()).toBe(loading); }); it('renders commit widget', () => { @@ -73,11 +74,17 @@ describe('Repository last commit component', () => { }); it('hides pipeline components when pipeline does not exist', () => { - factory(createCommitData({ pipeline: null })); + factory(createCommitData({ latestPipeline: null })); expect(vm.find('.js-commit-pipeline').exists()).toBe(false); }); + it('renders pipeline components', () => { + factory(); + + expect(vm.find('.js-commit-pipeline').exists()).toBe(true); + }); + it('hides author component when author does not exist', () => { factory(createCommitData({ author: null })); diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js index c17d5253997..15cf18700ed 100644 --- a/spec/frontend/test_setup.js +++ b/spec/frontend/test_setup.js @@ -3,6 +3,7 @@ import * as jqueryMatchers from 'custom-jquery-matchers'; import $ from 'jquery'; import Translate from '~/vue_shared/translate'; import axios from '~/lib/utils/axios_utils'; +import { config as testUtilsConfig } from '@vue/test-utils'; import { initializeTestTimeout } from './helpers/timeout'; import { loadHTMLFixture, setHTMLFixture } from './helpers/fixtures'; @@ -60,9 +61,21 @@ Object.assign(global, { preloadFixtures() {}, }); +Object.assign(global, { + MutationObserver() { + return { + disconnect() {}, + observe() {}, + }; + }, +}); + // custom-jquery-matchers was written for an old Jest version, we need to make it compatible Object.entries(jqueryMatchers).forEach(([matcherName, matcherFactory]) => { expect.extend({ [matcherName]: matcherFactory().compare, }); }); + +// Tech debt issue TBD +testUtilsConfig.logModifiedComponents = false; diff --git a/spec/graphql/gitlab_schema_spec.rb b/spec/graphql/gitlab_schema_spec.rb index 4076c1f824b..d36e428a8ee 100644 --- a/spec/graphql/gitlab_schema_spec.rb +++ b/spec/graphql/gitlab_schema_spec.rb @@ -113,7 +113,7 @@ describe GitlabSchema do end it "raises a meaningful error if a global id couldn't be generated" do - expect { described_class.id_from_object(build(:commit)) } + expect { described_class.id_from_object(build(:wiki_directory)) } .to raise_error(RuntimeError, /include `GlobalID::Identification` into/i) end end diff --git a/spec/graphql/types/commit_type_spec.rb b/spec/graphql/types/commit_type_spec.rb new file mode 100644 index 00000000000..5d8edcf254c --- /dev/null +++ b/spec/graphql/types/commit_type_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe GitlabSchema.types['Commit'] do + it { expect(described_class.graphql_name).to eq('Commit') } + + it { expect(described_class).to require_graphql_authorizations(:download_code) } + + it { expect(described_class).to have_graphql_fields(:id, :sha, :title, :description, :message, :authored_date, :author, :web_url, :latest_pipeline) } +end diff --git a/spec/graphql/types/tree/tree_type_spec.rb b/spec/graphql/types/tree/tree_type_spec.rb index b9c5570115e..23779d75600 100644 --- a/spec/graphql/types/tree/tree_type_spec.rb +++ b/spec/graphql/types/tree/tree_type_spec.rb @@ -5,5 +5,5 @@ require 'spec_helper' describe Types::Tree::TreeType do it { expect(described_class.graphql_name).to eq('Tree') } - it { expect(described_class).to have_graphql_fields(:trees, :submodules, :blobs) } + it { expect(described_class).to have_graphql_fields(:trees, :submodules, :blobs, :last_commit) } end diff --git a/spec/javascripts/boards/components/board_spec.js b/spec/javascripts/boards/components/board_spec.js index d08ee41802b..683783334c6 100644 --- a/spec/javascripts/boards/components/board_spec.js +++ b/spec/javascripts/boards/components/board_spec.js @@ -1,7 +1,6 @@ import Vue from 'vue'; -import '~/boards/services/board_service'; import Board from '~/boards/components/board'; -import '~/boards/models/list'; +import List from '~/boards/models/list'; import { mockBoardService } from '../mock_data'; describe('Board component', () => { @@ -27,7 +26,6 @@ describe('Board component', () => { disabled: false, issueLinkBase: '/', rootPath: '/', - // eslint-disable-next-line no-undef list: new List({ id: 1, position: 0, @@ -53,57 +51,62 @@ describe('Board component', () => { expect(vm.$el.classList.contains('is-expandable')).toBe(true); }); - it('board is expandable when list type is closed', done => { - vm.list.type = 'closed'; - - Vue.nextTick(() => { - expect(vm.$el.classList.contains('is-expandable')).toBe(true); - - done(); - }); + it('board is expandable when list type is closed', () => { + expect(new List({ id: 1, list_type: 'closed' }).isExpandable).toBe(true); }); - it('board is not expandable when list type is label', done => { - vm.list.type = 'label'; - vm.list.isExpandable = false; - - Vue.nextTick(() => { - expect(vm.$el.classList.contains('is-expandable')).toBe(false); + it('board is expandable when list type is label', () => { + expect(new List({ id: 1, list_type: 'closed' }).isExpandable).toBe(true); + }); - done(); - }); + it('board is not expandable when list type is blank', () => { + expect(new List({ id: 1, list_type: 'blank' }).isExpandable).toBe(false); }); - it('collapses when clicking header', done => { + it('does not collapse when clicking header', done => { + vm.list.isExpanded = true; vm.$el.querySelector('.board-header').click(); Vue.nextTick(() => { - expect(vm.$el.classList.contains('is-collapsed')).toBe(true); + expect(vm.$el.classList.contains('is-collapsed')).toBe(false); done(); }); }); - it('created sets isExpanded to true from localStorage', done => { - vm.$el.querySelector('.board-header').click(); + it('collapses when clicking the collapse icon', done => { + vm.list.isExpanded = true; - return Vue.nextTick() + Vue.nextTick() + .then(() => { + vm.$el.querySelector('.board-title-caret').click(); + }) .then(() => { expect(vm.$el.classList.contains('is-collapsed')).toBe(true); + done(); + }) + .catch(done.fail); + }); - // call created manually - vm.$options.created[0].call(vm); + it('expands when clicking the expand icon', done => { + vm.list.isExpanded = false; - return Vue.nextTick(); + Vue.nextTick() + .then(() => { + vm.$el.querySelector('.board-title-caret').click(); }) .then(() => { - expect(vm.$el.classList.contains('is-collapsed')).toBe(true); - + expect(vm.$el.classList.contains('is-collapsed')).toBe(false); done(); }) .catch(done.fail); }); + it('is expanded when created', () => { + expect(vm.list.isExpanded).toBe(true); + expect(vm.$el.classList.contains('is-collapsed')).toBe(false); + }); + it('does render add issue button', () => { expect(vm.$el.querySelector('.issue-count-badge-add-button')).not.toBeNull(); }); diff --git a/spec/javascripts/ide/components/repo_editor_spec.js b/spec/javascripts/ide/components/repo_editor_spec.js index 002b5a005b8..f832096701f 100644 --- a/spec/javascripts/ide/components/repo_editor_spec.js +++ b/spec/javascripts/ide/components/repo_editor_spec.js @@ -14,7 +14,10 @@ describe('RepoEditor', () => { let vm; beforeEach(done => { - const f = file(); + const f = { + ...file(), + viewMode: 'editor', + }; const RepoEditor = Vue.extend(repoEditor); vm = createComponentWithStore(RepoEditor, store, { @@ -41,12 +44,17 @@ describe('RepoEditor', () => { Editor.editorInstance.dispose(); }); - it('renders an ide container', done => { - Vue.nextTick(() => { - expect(vm.shouldHideEditor).toBeFalsy(); + const findEditor = () => vm.$el.querySelector('.multi-file-editor-holder'); + const changeRightPanelCollapsed = () => { + const { state } = vm.$store; - done(); - }); + state.rightPanelCollapsed = !state.rightPanelCollapsed; + }; + + it('renders an ide container', () => { + expect(vm.shouldHideEditor).toBeFalsy(); + expect(vm.showEditor).toBe(true); + expect(findEditor()).not.toHaveCss({ display: 'none' }); }); it('renders only an edit tab', done => { @@ -283,7 +291,7 @@ describe('RepoEditor', () => { }); it('calls updateDimensions when rightPanelCollapsed is changed', done => { - vm.$store.state.rightPanelCollapsed = true; + changeRightPanelCollapsed(); vm.$nextTick(() => { expect(vm.editor.updateDimensions).toHaveBeenCalled(); @@ -358,6 +366,47 @@ describe('RepoEditor', () => { }); }); + describe('when files view mode is preview', () => { + beforeEach(done => { + spyOn(vm.editor, 'updateDimensions'); + vm.file.viewMode = 'preview'; + vm.$nextTick(done); + }); + + it('should hide editor', () => { + expect(vm.showEditor).toBe(false); + expect(findEditor()).toHaveCss({ display: 'none' }); + }); + + it('should not update dimensions', done => { + changeRightPanelCollapsed(); + + vm.$nextTick() + .then(() => { + expect(vm.editor.updateDimensions).not.toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); + }); + + describe('when file view mode changes to editor', () => { + beforeEach(done => { + vm.file.viewMode = 'editor'; + + // one tick to trigger watch + vm.$nextTick() + // another tick needed until we can update dimensions + .then(() => vm.$nextTick()) + .then(done) + .catch(done.fail); + }); + + it('should update dimensions', () => { + expect(vm.editor.updateDimensions).toHaveBeenCalled(); + }); + }); + }); + it('calls removePendingTab when old file is pending', done => { spyOnProperty(vm, 'shouldHideEditor').and.returnValue(true); spyOn(vm, 'removePendingTab'); diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index ab8360193be..d3e10194d92 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -241,7 +241,7 @@ describe('Dashboard', () => { Vue.nextTick() .then(() => { const dropdownItems = component.$el.querySelectorAll( - '.js-environments-dropdown .dropdown-item.is-active', + '.js-environments-dropdown .dropdown-item.active', ); expect(dropdownItems.length).toEqual(1); diff --git a/spec/javascripts/registry/components/collapsible_container_spec.js b/spec/javascripts/registry/components/collapsible_container_spec.js index 9ed4b04324a..55017b3e26b 100644 --- a/spec/javascripts/registry/components/collapsible_container_spec.js +++ b/spec/javascripts/registry/components/collapsible_container_spec.js @@ -72,21 +72,15 @@ describe('collapsible registry container', () => { expect(findDeleteBtn()).not.toBeNull(); }); - describe('clicked on delete', () => { - beforeEach(done => { - findDeleteBtn().click(); - Vue.nextTick(done); - }); - - it('should open confirmation modal', () => { - expect(vm.$el.querySelector('#confirm-repo-deletion-modal')).not.toBeNull(); - }); + it('should call deleteItem when confirming deletion', done => { + findDeleteBtn().click(); + spyOn(vm, 'deleteItem').and.returnValue(Promise.resolve()); - it('should call deleteItem when confirming deletion', () => { - spyOn(vm, 'deleteItem').and.returnValue(Promise.resolve()); - vm.$el.querySelector('#confirm-repo-deletion-modal .btn-danger').click(); + Vue.nextTick(() => { + document.querySelector('#confirm-repo-deletion-modal .btn-danger').click(); expect(vm.deleteItem).toHaveBeenCalledWith(vm.repo); + done(); }); }); }); diff --git a/spec/javascripts/registry/components/table_registry_spec.js b/spec/javascripts/registry/components/table_registry_spec.js index d366c67a1b9..6a0b16f592e 100644 --- a/spec/javascripts/registry/components/table_registry_spec.js +++ b/spec/javascripts/registry/components/table_registry_spec.js @@ -46,23 +46,16 @@ describe('table registry', () => { expect(findDeleteBtn()).toBeDefined(); }); - describe('clicked on delete', () => { - beforeEach(done => { - findDeleteBtn().click(); - Vue.nextTick(done); - }); - - it('should open confirmation modal and set itemToBeDeleted properly', () => { - expect(vm.itemToBeDeleted).toEqual(firstImage); - expect(vm.$el.querySelector('#confirm-image-deletion-modal')).not.toBeNull(); - }); + it('should call deleteItem and reset itemToBeDeleted when confirming deletion', done => { + findDeleteBtn().click(); + spyOn(vm, 'deleteItem').and.returnValue(Promise.resolve()); - it('should call deleteItem and reset itemToBeDeleted when confirming deletion', () => { - spyOn(vm, 'deleteItem').and.returnValue(Promise.resolve()); - vm.$el.querySelector('#confirm-image-deletion-modal .btn-danger').click(); + Vue.nextTick(() => { + document.querySelector('#confirm-image-deletion-modal .btn-danger').click(); expect(vm.deleteItem).toHaveBeenCalledWith(firstImage); expect(vm.itemToBeDeleted).toBeNull(); + done(); }); }); }); diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js index 8c80a425581..2cc476ed52a 100644 --- a/spec/javascripts/test_bundle.js +++ b/spec/javascripts/test_bundle.js @@ -10,12 +10,16 @@ import VueResource from 'vue-resource'; import Translate from '~/vue_shared/translate'; import CheckEE from '~/vue_shared/mixins/is_ee'; import jasmineDiff from 'jasmine-diff'; +import { config as testUtilsConfig } from '@vue/test-utils'; import { getDefaultAdapter } from '~/lib/utils/axios_utils'; import { FIXTURES_PATH, TEST_HOST } from './test_constants'; import customMatchers from './matchers'; +// Tech debt issue TBD +testUtilsConfig.logModifiedComponents = false; + const isHeadlessChrome = /\bHeadlessChrome\//.test(navigator.userAgent); Vue.config.devtools = !isHeadlessChrome; Vue.config.productionTip = false; diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js index 48f812f0db4..253413ae43e 100644 --- a/spec/javascripts/vue_mr_widget/mock_data.js +++ b/spec/javascripts/vue_mr_widget/mock_data.js @@ -218,7 +218,8 @@ export default { '/root/acets-app/forks?continue%5Bnotice%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+has+been+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.+Try+to+cherry-pick+this+commit+again.&continue%5Bnotice_now%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+is+being+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.&continue%5Bto%5D=%2Froot%2Facets-app%2Fmerge_requests%2F22&namespace_key=1', email_patches_path: '/root/acets-app/merge_requests/22.patch', plain_diff_path: '/root/acets-app/merge_requests/22.diff', - status_path: '/root/acets-app/merge_requests/22.json', + merge_request_basic_path: '/root/acets-app/merge_requests/22.json?serializer=basic', + merge_request_widget_path: '/root/acets-app/merge_requests/22/widget.json', merge_check_path: '/root/acets-app/merge_requests/22/merge_check', ci_environments_status_url: '/root/acets-app/merge_requests/22/ci_environments_status', project_archived: false, diff --git a/spec/lib/gitlab/auth/ip_rate_limiter_spec.rb b/spec/lib/gitlab/auth/ip_rate_limiter_spec.rb new file mode 100644 index 00000000000..8d6bf45ab30 --- /dev/null +++ b/spec/lib/gitlab/auth/ip_rate_limiter_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Auth::IpRateLimiter, :use_clean_rails_memory_store_caching do + let(:ip) { '10.2.2.3' } + let(:whitelist) { ['127.0.0.1'] } + let(:options) do + { + enabled: true, + ip_whitelist: whitelist, + bantime: 1.minute, + findtime: 1.minute, + maxretry: 2 + } + end + + subject { described_class.new(ip) } + + before do + stub_rack_attack_setting(options) + end + + after do + subject.reset! + end + + describe '#register_fail!' do + it 'bans after 3 consecutive failures' do + expect(subject.banned?).to be_falsey + + 3.times { subject.register_fail! } + + expect(subject.banned?).to be_truthy + end + + shared_examples 'whitelisted IPs' do + it 'does not ban after max retry limit' do + expect(subject.banned?).to be_falsey + + 3.times { subject.register_fail! } + + expect(subject.banned?).to be_falsey + end + end + + context 'with a whitelisted netmask' do + before do + options[:ip_whitelist] = ['127.0.0.1', '10.2.2.0/24', 'bad'] + stub_rack_attack_setting(options) + end + + it_behaves_like 'whitelisted IPs' + end + + context 'with a whitelisted IP' do + before do + options[:ip_whitelist] = ['10.2.2.3'] + stub_rack_attack_setting(options) + end + + it_behaves_like 'whitelisted IPs' + end + end +end diff --git a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb index 51e16c99688..d88a2097ba2 100644 --- a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb +++ b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb @@ -17,15 +17,12 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do end context 'build has a deployment' do - let!(:deployment) { create(:deployment, deployable: build) } + let!(:deployment) { create(:deployment, deployable: build, cluster: cluster) } + let(:cluster) { nil } context 'and a cluster to deploy to' do let(:cluster) { create(:cluster, :group) } - before do - allow(build.deployment).to receive(:deployment_platform_cluster).and_return(cluster) - end - it { is_expected.to be_truthy } context 'and the cluster is not managed' do @@ -48,28 +45,21 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do end context 'and no cluster to deploy to' do - before do - expect(deployment.deployment_platform_cluster).to be_nil - end - it { is_expected.to be_falsey } end end end describe '#complete!' do - let!(:deployment) { create(:deployment, deployable: build) } + let!(:deployment) { create(:deployment, deployable: build, cluster: cluster) } let(:service) { double(execute: true) } + let(:cluster) { nil } subject { described_class.new(build).complete! } context 'completion is required' do let(:cluster) { create(:cluster, :group) } - before do - allow(build.deployment).to receive(:deployment_platform_cluster).and_return(cluster) - end - it 'creates a kubernetes namespace' do expect(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService) .to receive(:new) @@ -83,10 +73,6 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do end context 'completion is not required' do - before do - expect(deployment.deployment_platform_cluster).to be_nil - end - it 'does not create a namespace' do expect(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).not_to receive(:new) diff --git a/spec/lib/gitlab/graphql/loaders/pipeline_for_sha_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/pipeline_for_sha_loader_spec.rb new file mode 100644 index 00000000000..927476cc655 --- /dev/null +++ b/spec/lib/gitlab/graphql/loaders/pipeline_for_sha_loader_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe Gitlab::Graphql::Loaders::PipelineForShaLoader do + include GraphqlHelpers + + describe '#find_last' do + it 'batch-resolves latest pipeline' do + project = create(:project, :repository) + pipeline1 = create(:ci_pipeline, project: project, ref: project.default_branch, sha: project.commit.sha) + pipeline2 = create(:ci_pipeline, project: project, ref: project.default_branch, sha: project.commit.sha) + pipeline3 = create(:ci_pipeline, project: project, ref: 'improve/awesome', sha: project.commit('improve/awesome').sha) + + result = batch(max_queries: 1) do + [pipeline1.sha, pipeline3.sha].map { |sha| described_class.new(project, sha).find_last } + end + + expect(result).to contain_exactly(pipeline2, pipeline3) + end + end +end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 7a250603b6b..7baa52ffb4f 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -397,6 +397,7 @@ project: - incident_management_setting - merge_trains - designs +- project_aliases award_emoji: - awardable - user diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 6512fe80a3b..8be074f4b9b 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -6760,7 +6760,7 @@ }, { "id": 95, - "title": "JIRA", + "title": "Jira", "project_id": 5, "created_at": "2016-06-14T15:01:51.255Z", "updated_at": "2016-06-14T15:01:51.255Z", diff --git a/spec/lib/gitlab/issuable_sorter_spec.rb b/spec/lib/gitlab/issuable_sorter_spec.rb index 642a6cb6caa..5bd76bc6081 100644 --- a/spec/lib/gitlab/issuable_sorter_spec.rb +++ b/spec/lib/gitlab/issuable_sorter_spec.rb @@ -26,7 +26,7 @@ describe Gitlab::IssuableSorter do expect(described_class.sort(project1, unsorted)).to eq(sorted) end - context 'for JIRA issues' do + context 'for Jira issues' do let(:sorted) do [ExternalIssue.new('JIRA-1', project1), ExternalIssue.new('JIRA-2', project1), diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb index b0603d96eb2..da87df15746 100644 --- a/spec/lib/gitlab/metrics/system_spec.rb +++ b/spec/lib/gitlab/metrics/system_spec.rb @@ -52,13 +52,13 @@ describe Gitlab::Metrics::System do end describe '.cpu_time' do - it 'returns a Fixnum' do + it 'returns a Float' do expect(described_class.cpu_time).to be_an(Float) end end describe '.real_time' do - it 'returns a Fixnum' do + it 'returns a Float' do expect(described_class.real_time).to be_an(Float) end end diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb index d982053d92e..7513dbeeb6f 100644 --- a/spec/lib/gitlab/reference_extractor_spec.rb +++ b/spec/lib/gitlab/reference_extractor_spec.rb @@ -197,14 +197,14 @@ describe Gitlab::ReferenceExtractor do let(:issue) { create(:issue, project: project) } context 'when GitLab issues are enabled' do - it 'returns both JIRA and internal issues' do + it 'returns both Jira and internal issues' do subject.analyze("JIRA-123 and FOOBAR-4567 and #{issue.to_reference}") expect(subject.issues).to eq [ExternalIssue.new('JIRA-123', project), ExternalIssue.new('FOOBAR-4567', project), issue] end - it 'returns only JIRA issues if the internal one does not exists' do + it 'returns only Jira issues if the internal one does not exists' do subject.analyze("JIRA-123 and FOOBAR-4567 and #999") expect(subject.issues).to eq [ExternalIssue.new('JIRA-123', project), ExternalIssue.new('FOOBAR-4567', project)] @@ -217,7 +217,7 @@ describe Gitlab::ReferenceExtractor do project.save! end - it 'returns only JIRA issues' do + it 'returns only Jira issues' do subject.analyze("JIRA-123 and FOOBAR-4567 and #{issue.to_reference}") expect(subject.issues).to eq [ExternalIssue.new('JIRA-123', project), ExternalIssue.new('FOOBAR-4567', project)] diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 6ebc6337d50..55cea48b641 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -1886,6 +1886,17 @@ describe Ci::Pipeline, :mailer do end end + describe '.latest_for_shas' do + let(:sha) { 'abc' } + + it 'returns latest pipeline for sha' do + create(:ci_pipeline, sha: sha) + pipeline2 = create(:ci_pipeline, sha: sha) + + expect(described_class.latest_for_shas(sha)).to contain_exactly(pipeline2) + end + end + describe '.latest_successful_ids_per_project' do let(:projects) { create_list(:project, 2) } let!(:pipeline1) { create(:ci_pipeline, :success, project: projects[0]) } diff --git a/spec/models/concerns/deployable_spec.rb b/spec/models/concerns/deployable_spec.rb index 42bed9434f5..bb73dd8ade0 100644 --- a/spec/models/concerns/deployable_spec.rb +++ b/spec/models/concerns/deployable_spec.rb @@ -7,10 +7,6 @@ describe Deployable do let(:deployment) { job.deployment } let(:environment) { deployment&.environment } - before do - job.reload - end - context 'when the deployable object will deploy to production' do let!(:job) { create(:ci_build, :start_review_app) } @@ -26,6 +22,16 @@ describe Deployable do end end + context 'when the deployable object will deploy to a cluster' do + let(:project) { create(:project) } + let!(:cluster) { create(:cluster, :provided_by_user, projects: [project]) } + let!(:job) { create(:ci_build, :start_review_app, project: project) } + + it 'creates a deployment with cluster association' do + expect(deployment.cluster).to eq(cluster) + end + end + context 'when the deployable object will stop an environment' do let!(:job) { create(:ci_build, :stop_review_app) } diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb index f31e3e8821d..6034344d034 100644 --- a/spec/models/concerns/mentionable_spec.rb +++ b/spec/models/concerns/mentionable_spec.rb @@ -18,7 +18,7 @@ describe Mentionable do let(:project) { create(:project) } let(:mentionable) { Example.new } - it 'excludes JIRA references' do + it 'excludes Jira references' do allow(project).to receive_messages(jira_tracker?: true) mentionable.project = project diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index a433878f3bc..8d0eb0f4a06 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -7,6 +7,7 @@ describe Deployment do it { is_expected.to belong_to(:project).required } it { is_expected.to belong_to(:environment).required } + it { is_expected.to belong_to(:cluster).class_name('Clusters::Cluster') } it { is_expected.to belong_to(:user) } it { is_expected.to belong_to(:deployable) } @@ -294,6 +295,67 @@ describe Deployment do end end + describe '#has_metrics?' do + subject { deployment.has_metrics? } + + context 'when deployment is failed' do + let(:deployment) { create(:deployment, :failed) } + + it { is_expected.to be_falsy } + end + + context 'when deployment is success' do + let(:deployment) { create(:deployment, :success) } + + context 'without a monitoring service' do + it { is_expected.to be_falsy } + end + + context 'with a Prometheus Service' do + let(:prometheus_service) { double(:prometheus_service, can_query?: true) } + + before do + allow(deployment.project).to receive(:find_or_initialize_service).with('prometheus').and_return prometheus_service + end + + it { is_expected.to be_truthy } + end + + context 'with a Prometheus Service that cannot query' do + let(:prometheus_service) { double(:prometheus_service, can_query?: false) } + + before do + allow(deployment.project).to receive(:find_or_initialize_service).with('prometheus').and_return prometheus_service + end + + it { is_expected.to be_falsy } + end + + context 'with a cluster Prometheus' do + let(:deployment) { create(:deployment, :success, :on_cluster) } + let!(:prometheus) { create(:clusters_applications_prometheus, :installed, cluster: deployment.cluster) } + + before do + expect(deployment.cluster.application_prometheus).to receive(:can_query?).and_return(true) + end + + it { is_expected.to be_truthy } + end + + context 'fallback deployment platform' do + let(:cluster) { create(:cluster, :provided_by_user, environment_scope: '*', projects: [deployment.project]) } + let!(:prometheus) { create(:clusters_applications_prometheus, :installed, cluster: cluster) } + + before do + expect(deployment.project).to receive(:deployment_platform).and_return(cluster.platform) + expect(cluster.application_prometheus).to receive(:can_query?).and_return(true) + end + + it { is_expected.to be_truthy } + end + end + end + describe '#metrics' do let(:deployment) { create(:deployment, :success) } let(:prometheus_adapter) { double('prometheus_adapter', can_query?: true) } diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index 04ae9390436..fc08457a3c5 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -158,7 +158,7 @@ describe JiraService do WebMock.stub_request(:post, @remote_link_url).with(basic_auth: %w(gitlab_jira_username gitlab_jira_password)) end - it 'calls JIRA API' do + it 'calls Jira API' do @jira_service.close_issue(resource, ExternalIssue.new('JIRA-123', project)) expect(WebMock).to have_requested(:post, @comment_url).with( @@ -175,14 +175,14 @@ describe JiraService do # Check https://developer.atlassian.com/jiradev/jira-platform/guides/other/guide-jira-remote-issue-links/fields-in-remote-issue-links # for more information - it 'creates Remote Link reference in JIRA for comment' do + it 'creates Remote Link reference in Jira for comment' do @jira_service.close_issue(resource, ExternalIssue.new('JIRA-123', project)) favicon_path = "http://localhost/assets/#{find_asset('favicon.png').digest_path}" # Creates comment expect(WebMock).to have_requested(:post, @comment_url) - # Creates Remote Link in JIRA issue fields + # Creates Remote Link in Jira issue fields expect(WebMock).to have_requested(:post, @remote_link_url).with( body: hash_including( GlobalID: 'GitLab', @@ -319,7 +319,7 @@ describe JiraService do end context 'when the test succeeds' do - it 'tries to get JIRA project with URL when API URL not set' do + it 'tries to get Jira project with URL when API URL not set' do test_settings('jira.example.com') end @@ -327,7 +327,7 @@ describe JiraService do expect(test_settings).to eq( { success: true, result: { 'url' => 'http://url' } }) end - it 'tries to get JIRA project with API URL if set' do + it 'tries to get Jira project with API URL if set' do jira_service.update(api_url: 'http://jira.api.com') test_settings('jira.api.com') end @@ -462,7 +462,7 @@ describe JiraService do end it 'is initialized' do - expect(@service.title).to eq('JIRA') + expect(@service.title).to eq('Jira') expect(@service.description).to eq('Jira issue tracker') end end diff --git a/spec/requests/api/graphql/project/tree/tree_spec.rb b/spec/requests/api/graphql/project/tree/tree_spec.rb index b07aa1e12d3..94128cc21ee 100644 --- a/spec/requests/api/graphql/project/tree/tree_spec.rb +++ b/spec/requests/api/graphql/project/tree/tree_spec.rb @@ -33,6 +33,12 @@ describe 'getting a tree in a project' do expect(graphql_data['project']['repository']['tree']['submodules']['edges']).to eq([]) expect(graphql_data['project']['repository']['tree']['blobs']['edges']).to eq([]) end + + it 'returns null commit' do + post_graphql(query, current_user: current_user) + + expect(graphql_data['project']['repository']['last_commit']).to be_nil + end end context 'when ref does not exist' do @@ -45,6 +51,12 @@ describe 'getting a tree in a project' do expect(graphql_data['project']['repository']['tree']['submodules']['edges']).to eq([]) expect(graphql_data['project']['repository']['tree']['blobs']['edges']).to eq([]) end + + it 'returns null commit' do + post_graphql(query, current_user: current_user) + + expect(graphql_data['project']['repository']['last_commit']).to be_nil + end end context 'when ref and path exist' do @@ -61,6 +73,12 @@ describe 'getting a tree in a project' do expect(graphql_data['project']['repository']['tree']['blobs']['edges'].size).to be > 0 expect(graphql_data['project']['repository']['tree']['submodules']['edges'].size).to be > 0 end + + it 'returns tree latest commit' do + post_graphql(query, current_user: current_user) + + expect(graphql_data['project']['repository']['tree']['lastCommit']).to be_present + end end context 'when current user is nil' do diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index 2fbe5468b21..aa759ac9edc 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -58,7 +58,7 @@ describe MergeRequests::MergeService do expect(issue.reload.closed?).to be_truthy end - context 'with JIRA integration' do + context 'with Jira integration' do include JiraServiceHelper let(:jira_tracker) { project.create_jira_service } @@ -72,7 +72,7 @@ describe MergeRequests::MergeService do allow(merge_request).to receive(:commits).and_return([commit]) end - it 'closes issues on JIRA issue tracker' do + it 'closes issues on Jira issue tracker' do jira_issue = ExternalIssue.new('JIRA-123', project) stub_jira_urls(jira_issue) commit = double('commit', safe_message: "Fixes #{jira_issue.to_reference}") @@ -98,7 +98,7 @@ describe MergeRequests::MergeService do end context "wrong issue markdown" do - it 'does not close issues on JIRA issue tracker' do + it 'does not close issues on Jira issue tracker' do jira_issue = ExternalIssue.new('#JIRA-123', project) stub_jira_urls(jira_issue) commit = double('commit', safe_message: "Fixes #{jira_issue.to_reference}") diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 30a867fa7ba..93fe3290d8b 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -750,7 +750,7 @@ describe SystemNoteService do end end - describe 'JIRA integration' do + describe 'Jira integration' do include JiraServiceHelper let(:project) { create(:jira_project, :repository) } diff --git a/spec/support/api/boards_shared_examples.rb b/spec/support/api/boards_shared_examples.rb index 592962ebf7c..3abb5096a7a 100644 --- a/spec/support/api/boards_shared_examples.rb +++ b/spec/support/api/boards_shared_examples.rb @@ -14,6 +14,16 @@ shared_examples_for 'group and project boards' do |route_definition, ee = false| end end + it 'avoids N+1 queries' do + pat = create(:personal_access_token, user: user) + control = ActiveRecord::QueryRecorder.new { get api(root_url, personal_access_token: pat) } + + create(:milestone, "#{board_parent.class.name.underscore}": board_parent) + create(:board, "#{board_parent.class.name.underscore}": board_parent) + + expect { get api(root_url, personal_access_token: pat) }.not_to exceed_query_limit(control) + end + describe "GET #{route_definition}" do context "when unauthenticated" do it "returns authentication error" do diff --git a/spec/support/helpers/jira_service_helper.rb b/spec/support/helpers/jira_service_helper.rb index 477bbf1c2e0..7e955f3d593 100644 --- a/spec/support/helpers/jira_service_helper.rb +++ b/spec/support/helpers/jira_service_helper.rb @@ -4,7 +4,7 @@ module JiraServiceHelper def jira_service_settings properties = { - title: "JIRA tracker", + title: "Jira tracker", url: JIRA_URL, username: 'jira-user', password: 'my-secret-password', diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb index 0d591f038ce..c372a3f0e49 100644 --- a/spec/support/helpers/stub_configuration.rb +++ b/spec/support/helpers/stub_configuration.rb @@ -95,6 +95,11 @@ module StubConfiguration allow(Gitlab.config.gitlab_shell).to receive_messages(to_settings(messages)) end + def stub_rack_attack_setting(messages) + allow(Gitlab.config.rack_attack).to receive(:git_basic_auth).and_return(messages) + allow(Gitlab.config.rack_attack.git_basic_auth).to receive_messages(to_settings(messages)) + end + private # Modifies stubbed messages to also stub possible predicate versions diff --git a/spec/support/shared_examples/ci_trace_shared_examples.rb b/spec/support/shared_examples/ci_trace_shared_examples.rb index f985b2dcbba..ab0550e2613 100644 --- a/spec/support/shared_examples/ci_trace_shared_examples.rb +++ b/spec/support/shared_examples/ci_trace_shared_examples.rb @@ -270,7 +270,7 @@ shared_examples_for 'common trace features' do include ExclusiveLeaseHelpers before do - stub_exclusive_lease_taken("trace:write:lock:#{trace.job.id}", timeout: 1.minute) + stub_exclusive_lease_taken("trace:write:lock:#{trace.job.id}", timeout: 10.minutes) end it 'blocks concurrent archiving' do diff --git a/spec/support/shared_examples/services/boards/issues_move_service.rb b/spec/support/shared_examples/services/boards/issues_move_service.rb index 9dbd1d8e867..5359831f8f8 100644 --- a/spec/support/shared_examples/services/boards/issues_move_service.rb +++ b/spec/support/shared_examples/services/boards/issues_move_service.rb @@ -1,8 +1,17 @@ shared_examples 'issues move service' do |group| + shared_examples 'updating timestamps' do + it 'updates updated_at' do + expect {described_class.new(parent, user, params).execute(issue)} + .to change {issue.reload.updated_at} + end + end + context 'when moving an issue between lists' do let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) } let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list2.id } } + it_behaves_like 'updating timestamps' + it 'delegates the label changes to Issues::UpdateService' do service = double(:service) expect(Issues::UpdateService).to receive(:new).and_return(service) @@ -24,6 +33,8 @@ shared_examples 'issues move service' do |group| let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing, regression]) } let(:params) { { board_id: board1.id, from_list_id: list2.id, to_list_id: closed.id } } + it_behaves_like 'updating timestamps' + it 'delegates the close proceedings to Issues::CloseService' do expect_any_instance_of(Issues::CloseService).to receive(:execute).with(issue).once @@ -46,6 +57,8 @@ shared_examples 'issues move service' do |group| let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing, regression], milestone: milestone) } let(:params) { { board_id: board1.id, from_list_id: list2.id, to_list_id: backlog.id } } + it_behaves_like 'updating timestamps' + it 'keeps labels and milestone' do described_class.new(parent, user, params).execute(issue) issue.reload @@ -59,6 +72,8 @@ shared_examples 'issues move service' do |group| let(:issue) { create(:labeled_issue, :closed, project: project, labels: [bug]) } let(:params) { { board_id: board1.id, from_list_id: closed.id, to_list_id: list2.id } } + it_behaves_like 'updating timestamps' + it 'delegates the re-open proceedings to Issues::ReopenService' do expect_any_instance_of(Issues::ReopenService).to receive(:execute).with(issue).once @@ -75,10 +90,13 @@ shared_examples 'issues move service' do |group| end context 'when moving to same list' do - let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) } - let(:issue1) { create(:labeled_issue, project: project, labels: [bug, development]) } - let(:issue2) { create(:labeled_issue, project: project, labels: [bug, development]) } - let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list1.id } } + let(:assignee) { create(:user) } + let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list1.id } } + let(:issue1) { create(:labeled_issue, project: project, labels: [bug, development]) } + let(:issue2) { create(:labeled_issue, project: project, labels: [bug, development]) } + let(:issue) do + create(:labeled_issue, project: project, labels: [bug, development], assignees: [assignee]) + end it 'returns false' do expect(described_class.new(parent, user, params).execute(issue)).to eq false @@ -90,18 +108,36 @@ shared_examples 'issues move service' do |group| expect(issue.reload.labels).to contain_exactly(bug, development) end - it 'sorts issues' do - [issue, issue1, issue2].each do |issue| - issue.move_to_end && issue.save! - end + it 'keeps issues assignees' do + described_class.new(parent, user, params).execute(issue) + + expect(issue.reload.assignees).to contain_exactly(assignee) + end - params.merge!(move_after_id: issue1.id, move_before_id: issue2.id) + it 'sorts issues' do + reorder_issues(params, issues: [issue, issue1, issue2]) described_class.new(parent, user, params).execute(issue) expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position) end + it 'does not update updated_at' do + reorder_issues(params, issues: [issue, issue1, issue2]) + + updated_at = issue.updated_at + updated_at1 = issue1.updated_at + updated_at2 = issue2.updated_at + + Timecop.travel(1.minute.from_now) do + described_class.new(parent, user, params).execute(issue) + end + + expect(issue.reload.updated_at.change(usec: 0)).to eq updated_at.change(usec: 0) + expect(issue1.reload.updated_at.change(usec: 0)).to eq updated_at1.change(usec: 0) + expect(issue2.reload.updated_at.change(usec: 0)).to eq updated_at2.change(usec: 0) + end + if group context 'when on a group board' do it 'sends the board_group_id parameter' do @@ -114,5 +150,13 @@ shared_examples 'issues move service' do |group| end end end + + def reorder_issues(params, issues: []) + issues.each do |issue| + issue.move_to_end && issue.save! + end + + params.merge!(move_after_id: issues[1].id, move_before_id: issues[2].id) + end end end diff --git a/spec/tasks/migrate/schema_check_rake_spec.rb b/spec/tasks/migrate/schema_check_rake_spec.rb new file mode 100644 index 00000000000..72fb1363dfb --- /dev/null +++ b/spec/tasks/migrate/schema_check_rake_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'rake' + +describe 'schema_version_check rake task', :quarantine do + include StubENV + + before :all do + Rake.application.rake_require 'active_record/railties/databases' + Rake.application.rake_require 'tasks/migrate/schema_check' + + # empty task as env is already loaded + Rake::Task.define_task :environment + end + + before do + # Stub out db tasks + allow(ActiveRecord::Tasks::DatabaseTasks).to receive(:migrate).and_return(true) + allow(ActiveRecord::Migrator).to receive(:current_version).and_return(Gitlab::Database::MIN_SCHEMA_VERSION) + + # Ensure our check can re-run each time + Rake::Task[:schema_version_check].reenable + end + + it 'allows migrations on databases meeting the min schema version requirement' do + expect { run_rake_task('db:migrate') }.not_to raise_error + end + + it 'raises an error when schema version is too old to migrate' do + allow(ActiveRecord::Migrator).to receive(:current_version).and_return(25) + expect { run_rake_task('db:migrate') }.to raise_error(RuntimeError, /current database version is too old to be migrated/) + end + + it 'skips running validation when passed the skip env variable' do + stub_env('SKIP_SCHEMA_VERSION_CHECK', 'true') + allow(ActiveRecord::Migrator).to receive(:current_version).and_return(25) + expect { run_rake_task('db:migrate') }.not_to raise_error + end + + it 'allows migrations on fresh databases' do + allow(ActiveRecord::Migrator).to receive(:current_version).and_return(0) + expect { run_rake_task('db:migrate') }.not_to raise_error + end + + def run_rake_task(task_name) + Rake::Task[task_name].reenable + Rake.application.invoke_task task_name + end +end diff --git a/spec/views/projects/services/_form.haml_spec.rb b/spec/views/projects/services/_form.haml_spec.rb index 85167bca115..06e159f103b 100644 --- a/spec/views/projects/services/_form.haml_spec.rb +++ b/spec/views/projects/services/_form.haml_spec.rb @@ -28,7 +28,7 @@ describe 'projects/services/_form' do expect(rendered).to have_content('Event will be triggered when a merge request is created/updated/merged') end - context 'when service is JIRA' do + context 'when service is Jira' do let(:project) { create(:jira_project) } before do @@ -38,8 +38,8 @@ describe 'projects/services/_form' do it 'display merge_request_events and commit_events descriptions' do render - expect(rendered).to have_content('JIRA comments will be created when an issue gets referenced in a commit.') - expect(rendered).to have_content('JIRA comments will be created when an issue gets referenced in a merge request.') + expect(rendered).to have_content('Jira comments will be created when an issue gets referenced in a commit.') + expect(rendered).to have_content('Jira comments will be created when an issue gets referenced in a merge request.') end end end |