diff options
Diffstat (limited to 'spec')
-rw-r--r-- | spec/frontend/boards/board_blank_state_spec.js (renamed from spec/javascripts/boards/board_blank_state_spec.js) | 22 | ||||
-rw-r--r-- | spec/frontend/boards/board_new_issue_spec.js (renamed from spec/javascripts/boards/board_new_issue_spec.js) | 141 | ||||
-rw-r--r-- | spec/frontend/boards/components/board_form_spec.js | 47 | ||||
-rw-r--r-- | spec/frontend/boards/issue_spec.js (renamed from spec/javascripts/boards/issue_spec.js) | 4 | ||||
-rw-r--r-- | spec/frontend/boards/mock_data.js | 53 | ||||
-rw-r--r-- | spec/javascripts/boards/components/board_form_spec.js | 56 | ||||
-rw-r--r-- | spec/javascripts/boards/mock_data.js | 54 | ||||
-rw-r--r-- | spec/lib/gitlab/kubernetes/helm/install_command_spec.rb | 14 | ||||
-rw-r--r-- | spec/services/clusters/management/create_project_service_spec.rb | 126 |
9 files changed, 309 insertions, 208 deletions
diff --git a/spec/javascripts/boards/board_blank_state_spec.js b/spec/frontend/boards/board_blank_state_spec.js index b64859ec32a..3ffdda52f58 100644 --- a/spec/javascripts/boards/board_blank_state_spec.js +++ b/spec/frontend/boards/board_blank_state_spec.js @@ -11,9 +11,9 @@ describe('Boards blank state', () => { boardsStore.create(); - spyOn(boardsStore, 'addList').and.stub(); - spyOn(boardsStore, 'removeList').and.stub(); - spyOn(boardsStore, 'generateDefaultLists').and.callFake( + jest.spyOn(boardsStore, 'addList').mockImplementation(); + jest.spyOn(boardsStore, 'removeList').mockImplementation(); + jest.spyOn(boardsStore, 'generateDefaultLists').mockImplementation( () => new Promise((resolve, reject) => { if (fail) { @@ -39,7 +39,7 @@ describe('Boards blank state', () => { vm = new Comp(); - setTimeout(() => { + setImmediate(() => { vm.$mount(); done(); }); @@ -60,7 +60,7 @@ describe('Boards blank state', () => { it('clears blank state', done => { vm.$el.querySelector('.btn-default').click(); - setTimeout(() => { + setImmediate(() => { expect(boardsStore.welcomeIsHidden()).toBeTruthy(); done(); @@ -70,15 +70,11 @@ describe('Boards blank state', () => { it('creates pre-defined labels', done => { vm.$el.querySelector('.btn-success').click(); - setTimeout(() => { + setImmediate(() => { expect(boardsStore.addList).toHaveBeenCalledTimes(2); - expect(boardsStore.addList).toHaveBeenCalledWith( - jasmine.objectContaining({ title: 'To Do' }), - ); + expect(boardsStore.addList).toHaveBeenCalledWith(expect.objectContaining({ title: 'To Do' })); - expect(boardsStore.addList).toHaveBeenCalledWith( - jasmine.objectContaining({ title: 'Doing' }), - ); + expect(boardsStore.addList).toHaveBeenCalledWith(expect.objectContaining({ title: 'Doing' })); done(); }); @@ -89,7 +85,7 @@ describe('Boards blank state', () => { vm.$el.querySelector('.btn-success').click(); - setTimeout(() => { + setImmediate(() => { expect(boardsStore.welcomeIsHidden()).toBeFalsy(); expect(boardsStore.removeList).toHaveBeenCalledWith(undefined, 'label'); diff --git a/spec/javascripts/boards/board_new_issue_spec.js b/spec/frontend/boards/board_new_issue_spec.js index 8e4093cc25c..4eb7f0c131e 100644 --- a/spec/javascripts/boards/board_new_issue_spec.js +++ b/spec/frontend/boards/board_new_issue_spec.js @@ -1,5 +1,6 @@ /* global List */ +import $ from 'jquery'; import Vue from 'vue'; import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; @@ -14,6 +15,9 @@ describe('Issue boards new issue form', () => { let list; let mock; let newIssueMock; + const jQueryMock = { + enable: jest.fn(), + }; const promiseReturn = { data: { iid: 100, @@ -28,7 +32,7 @@ describe('Issue boards new issue form', () => { return vm.submit(dummySubmitEvent); }; - beforeEach(done => { + beforeEach(() => { setFixtures('<div class="test-container"></div>'); const BoardNewIssueComp = Vue.extend(boardNewIssue); @@ -41,7 +45,7 @@ describe('Issue boards new issue form', () => { list = new List(listObj); newIssueMock = Promise.resolve(promiseReturn); - spyOn(list, 'newIssue').and.callFake(() => newIssueMock); + jest.spyOn(list, 'newIssue').mockImplementation(() => newIssueMock); vm = new BoardNewIssueComp({ propsData: { @@ -49,9 +53,9 @@ describe('Issue boards new issue form', () => { }, }).$mount(document.querySelector('.test-container')); - Vue.nextTick() - .then(done) - .catch(done.fail); + $.fn.extend(jQueryMock); + + return Vue.nextTick(); }); afterEach(() => { @@ -59,142 +63,116 @@ describe('Issue boards new issue form', () => { mock.restore(); }); - it('calls submit if submit button is clicked', done => { - spyOn(vm, 'submit').and.callFake(e => e.preventDefault()); + it('calls submit if submit button is clicked', () => { + jest.spyOn(vm, 'submit').mockImplementation(e => e.preventDefault()); vm.title = 'Testing Title'; - Vue.nextTick() - .then(() => { - vm.$el.querySelector('.btn-success').click(); + return Vue.nextTick().then(() => { + vm.$el.querySelector('.btn-success').click(); - expect(vm.submit.calls.count()).toBe(1); - }) - .then(done) - .catch(done.fail); + expect(vm.submit.mock.calls.length).toBe(1); + }); }); it('disables submit button if title is empty', () => { expect(vm.$el.querySelector('.btn-success').disabled).toBe(true); }); - it('enables submit button if title is not empty', done => { + it('enables submit button if title is not empty', () => { vm.title = 'Testing Title'; - Vue.nextTick() - .then(() => { - expect(vm.$el.querySelector('.form-control').value).toBe('Testing Title'); - expect(vm.$el.querySelector('.btn-success').disabled).not.toBe(true); - }) - .then(done) - .catch(done.fail); + return Vue.nextTick().then(() => { + expect(vm.$el.querySelector('.form-control').value).toBe('Testing Title'); + expect(vm.$el.querySelector('.btn-success').disabled).not.toBe(true); + }); }); - it('clears title after clicking cancel', done => { + it('clears title after clicking cancel', () => { vm.$el.querySelector('.btn-default').click(); - Vue.nextTick() - .then(() => { - expect(vm.title).toBe(''); - }) - .then(done) - .catch(done.fail); + return Vue.nextTick().then(() => { + expect(vm.title).toBe(''); + }); }); - it('does not create new issue if title is empty', done => { - submitIssue() - .then(() => { - expect(list.newIssue).not.toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); + it('does not create new issue if title is empty', () => { + return submitIssue().then(() => { + expect(list.newIssue).not.toHaveBeenCalled(); + }); }); describe('submit success', () => { - it('creates new issue', done => { + it('creates new issue', () => { vm.title = 'submit title'; - Vue.nextTick() + return Vue.nextTick() .then(submitIssue) .then(() => { expect(list.newIssue).toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); + }); }); - it('enables button after submit', done => { + it('enables button after submit', () => { vm.title = 'submit issue'; - Vue.nextTick() + return Vue.nextTick() .then(submitIssue) .then(() => { - expect(vm.$el.querySelector('.btn-success').disabled).toBe(false); - }) - .then(done) - .catch(done.fail); + expect(jQueryMock.enable).toHaveBeenCalled(); + }); }); - it('clears title after submit', done => { + it('clears title after submit', () => { vm.title = 'submit issue'; - Vue.nextTick() + return Vue.nextTick() .then(submitIssue) .then(() => { expect(vm.title).toBe(''); - }) - .then(done) - .catch(done.fail); + }); }); - it('sets detail issue after submit', done => { + it('sets detail issue after submit', () => { expect(boardsStore.detail.issue.title).toBe(undefined); vm.title = 'submit issue'; - Vue.nextTick() + return Vue.nextTick() .then(submitIssue) .then(() => { expect(boardsStore.detail.issue.title).toBe('submit issue'); - }) - .then(done) - .catch(done.fail); + }); }); - it('sets detail list after submit', done => { + it('sets detail list after submit', () => { vm.title = 'submit issue'; - Vue.nextTick() + return Vue.nextTick() .then(submitIssue) .then(() => { expect(boardsStore.detail.list.id).toBe(list.id); - }) - .then(done) - .catch(done.fail); + }); }); - it('sets detail weight after submit', done => { + it('sets detail weight after submit', () => { boardsStore.weightFeatureAvailable = true; vm.title = 'submit issue'; - Vue.nextTick() + return Vue.nextTick() .then(submitIssue) .then(() => { expect(boardsStore.detail.list.weight).toBe(list.weight); - }) - .then(done) - .catch(done.fail); + }); }); - it('does not set detail weight after submit', done => { + it('does not set detail weight after submit', () => { boardsStore.weightFeatureAvailable = false; vm.title = 'submit issue'; - Vue.nextTick() + return Vue.nextTick() .then(submitIssue) .then(() => { expect(boardsStore.detail.list.weight).toBe(list.weight); - }) - .then(done) - .catch(done.fail); + }); }); }); @@ -204,24 +182,21 @@ describe('Issue boards new issue form', () => { vm.title = 'error'; }); - it('removes issue', done => { - Vue.nextTick() + it('removes issue', () => { + const lengthBefore = list.issues.length; + return Vue.nextTick() .then(submitIssue) .then(() => { - expect(list.issues.length).toBe(1); - }) - .then(done) - .catch(done.fail); + expect(list.issues.length).toBe(lengthBefore); + }); }); - it('shows error', done => { - Vue.nextTick() + it('shows error', () => { + return Vue.nextTick() .then(submitIssue) .then(() => { expect(vm.error).toBe(true); - }) - .then(done) - .catch(done.fail); + }); }); }); }); diff --git a/spec/frontend/boards/components/board_form_spec.js b/spec/frontend/boards/components/board_form_spec.js new file mode 100644 index 00000000000..ee427bc2154 --- /dev/null +++ b/spec/frontend/boards/components/board_form_spec.js @@ -0,0 +1,47 @@ +import { mount } from '@vue/test-utils'; + +import boardsStore from '~/boards/stores/boards_store'; +import boardForm from '~/boards/components/board_form.vue'; +import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue'; + +describe('board_form.vue', () => { + let wrapper; + + const propsData = { + canAdminBoard: false, + labelsPath: `${gl.TEST_HOST}/labels/path`, + milestonePath: `${gl.TEST_HOST}/milestone/path`, + }; + + const findModal = () => wrapper.find(DeprecatedModal); + + beforeEach(() => { + boardsStore.state.currentPage = 'edit'; + wrapper = mount(boardForm, { propsData }); + }); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + describe('methods', () => { + describe('cancel', () => { + it('resets currentPage', () => { + wrapper.vm.cancel(); + expect(boardsStore.state.currentPage).toBe(''); + }); + }); + }); + + describe('buttons', () => { + it('cancel button triggers cancel()', () => { + wrapper.setMethods({ cancel: jest.fn() }); + findModal().vm.$emit('cancel'); + + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.vm.cancel).toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/spec/javascripts/boards/issue_spec.js b/spec/frontend/boards/issue_spec.js index 181e7af7451..ff72edaa695 100644 --- a/spec/javascripts/boards/issue_spec.js +++ b/spec/frontend/boards/issue_spec.js @@ -174,7 +174,7 @@ describe('Issue model', () => { describe('update', () => { it('passes assignee ids when there are assignees', done => { - spyOn(axios, 'patch').and.callFake((url, data) => { + jest.spyOn(axios, 'patch').mockImplementation((url, data) => { expect(data.issue.assignee_ids).toEqual([1]); done(); return Promise.resolve(); @@ -184,7 +184,7 @@ describe('Issue model', () => { }); it('passes assignee ids of [0] when there are no assignees', done => { - spyOn(axios, 'patch').and.callFake((url, data) => { + jest.spyOn(axios, 'patch').mockImplementation((url, data) => { expect(data.issue.assignee_ids).toEqual([0]); done(); return Promise.resolve(); diff --git a/spec/frontend/boards/mock_data.js b/spec/frontend/boards/mock_data.js index 05f73e0d8dc..fa4154676a2 100644 --- a/spec/frontend/boards/mock_data.js +++ b/spec/frontend/boards/mock_data.js @@ -1,3 +1,5 @@ +import boardsStore from '~/boards/stores/boards_store'; + export const boardObj = { id: 1, name: 'test', @@ -89,3 +91,54 @@ export const mockMilestone = { start_date: '2018-01-01', due_date: '2019-12-31', }; + +export const BoardsMockData = { + GET: { + '/test/-/boards/1/lists/300/issues?id=300&page=1': { + issues: [ + { + title: 'Testing', + id: 1, + iid: 1, + confidential: false, + labels: [], + assignees: [], + }, + ], + }, + '/test/issue-boards/-/milestones.json': [ + { + id: 1, + title: 'test', + }, + ], + }, + POST: { + '/test/-/boards/1/lists': listObj, + }, + PUT: { + '/test/issue-boards/-/board/1/lists{/id}': {}, + }, + DELETE: { + '/test/issue-boards/-/board/1/lists{/id}': {}, + }, +}; + +export const boardsMockInterceptor = config => { + const body = BoardsMockData[config.method.toUpperCase()][config.url]; + return [200, body]; +}; + +export const setMockEndpoints = (opts = {}) => { + const boardsEndpoint = opts.boardsEndpoint || '/test/issue-boards/-/boards.json'; + const listsEndpoint = opts.listsEndpoint || '/test/-/boards/1/lists'; + const bulkUpdatePath = opts.bulkUpdatePath || ''; + const boardId = opts.boardId || '1'; + + boardsStore.setEndpoints({ + boardsEndpoint, + listsEndpoint, + bulkUpdatePath, + boardId, + }); +}; diff --git a/spec/javascripts/boards/components/board_form_spec.js b/spec/javascripts/boards/components/board_form_spec.js deleted file mode 100644 index fd1c79d44e1..00000000000 --- a/spec/javascripts/boards/components/board_form_spec.js +++ /dev/null @@ -1,56 +0,0 @@ -import $ from 'jquery'; -import Vue from 'vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import boardsStore from '~/boards/stores/boards_store'; -import boardForm from '~/boards/components/board_form.vue'; - -describe('board_form.vue', () => { - const props = { - canAdminBoard: false, - labelsPath: `${gl.TEST_HOST}/labels/path`, - milestonePath: `${gl.TEST_HOST}/milestone/path`, - }; - let vm; - - beforeEach(() => { - spyOn($, 'ajax'); - boardsStore.state.currentPage = 'edit'; - const Component = Vue.extend(boardForm); - vm = mountComponent(Component, props); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('methods', () => { - describe('cancel', () => { - it('resets currentPage', done => { - vm.cancel(); - - Vue.nextTick() - .then(() => { - expect(boardsStore.state.currentPage).toBe(''); - }) - .then(done) - .catch(done.fail); - }); - }); - }); - - describe('buttons', () => { - it('cancel button triggers cancel()', done => { - spyOn(vm, 'cancel'); - - Vue.nextTick() - .then(() => { - const cancelButton = vm.$el.querySelector('button[data-dismiss="modal"]'); - cancelButton.click(); - - expect(vm.cancel).toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); - }); - }); -}); diff --git a/spec/javascripts/boards/mock_data.js b/spec/javascripts/boards/mock_data.js index fcb5d9cfa08..8b39ad1abb4 100644 --- a/spec/javascripts/boards/mock_data.js +++ b/spec/javascripts/boards/mock_data.js @@ -1,55 +1 @@ -import boardsStore from '~/boards/stores/boards_store'; -import { listObj } from '../../frontend/boards/mock_data'; - export * from '../../frontend/boards/mock_data'; - -export const BoardsMockData = { - GET: { - '/test/-/boards/1/lists/300/issues?id=300&page=1': { - issues: [ - { - title: 'Testing', - id: 1, - iid: 1, - confidential: false, - labels: [], - assignees: [], - }, - ], - }, - '/test/issue-boards/-/milestones.json': [ - { - id: 1, - title: 'test', - }, - ], - }, - POST: { - '/test/-/boards/1/lists': listObj, - }, - PUT: { - '/test/issue-boards/-/board/1/lists{/id}': {}, - }, - DELETE: { - '/test/issue-boards/-/board/1/lists{/id}': {}, - }, -}; - -export const boardsMockInterceptor = config => { - const body = BoardsMockData[config.method.toUpperCase()][config.url]; - return [200, body]; -}; - -export const setMockEndpoints = (opts = {}) => { - const boardsEndpoint = opts.boardsEndpoint || '/test/issue-boards/-/boards.json'; - const listsEndpoint = opts.listsEndpoint || '/test/-/boards/1/lists'; - const bulkUpdatePath = opts.bulkUpdatePath || ''; - const boardId = opts.boardId || '1'; - - boardsStore.setEndpoints({ - boardsEndpoint, - listsEndpoint, - bulkUpdatePath, - boardId, - }); -}; diff --git a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb index 9c04e101e78..f94ceae362a 100644 --- a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb @@ -41,6 +41,8 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do <<~EOS.squish helm upgrade app-name chart-name --install + --atomic + --cleanup-on-fail --reset-values --version 1.2.3 --set rbac.create\\=false,rbac.enabled\\=false @@ -79,6 +81,8 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do <<~EOS.squish helm upgrade app-name chart-name --install + --atomic + --cleanup-on-fail --reset-values #{tls_flags} --version 1.2.3 @@ -109,6 +113,8 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do <<~EOS.squish helm upgrade app-name chart-name --install + --atomic + --cleanup-on-fail --reset-values --version 1.2.3 --set rbac.create\\=true,rbac.enabled\\=true @@ -140,6 +146,8 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do <<~EOS.squish helm upgrade app-name chart-name --install + --atomic + --cleanup-on-fail --reset-values --version 1.2.3 --set rbac.create\\=false,rbac.enabled\\=false @@ -171,6 +179,8 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do <<~EOS.squish helm upgrade app-name chart-name --install + --atomic + --cleanup-on-fail --reset-values --version 1.2.3 --set rbac.create\\=false,rbac.enabled\\=false @@ -200,6 +210,8 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do <<~EOS.squish helm upgrade app-name chart-name --install + --atomic + --cleanup-on-fail --reset-values --version 1.2.3 --set rbac.create\\=false,rbac.enabled\\=false @@ -229,6 +241,8 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do <<~EOS.squish helm upgrade app-name chart-name --install + --atomic + --cleanup-on-fail --reset-values --set rbac.create\\=false,rbac.enabled\\=false --namespace gitlab-managed-apps diff --git a/spec/services/clusters/management/create_project_service_spec.rb b/spec/services/clusters/management/create_project_service_spec.rb new file mode 100644 index 00000000000..b7764b7840c --- /dev/null +++ b/spec/services/clusters/management/create_project_service_spec.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Clusters::Management::CreateProjectService do + let(:cluster) { create(:cluster, :project) } + let(:current_user) { create(:user) } + + subject { described_class.new(cluster, current_user: current_user).execute } + + shared_examples 'management project is not required' do + it 'does not create a project' do + expect { subject }.not_to change(cluster, :management_project) + end + end + + context ':auto_create_cluster_management_project feature flag is disabled' do + before do + stub_feature_flags(auto_create_cluster_management_project: false) + end + + include_examples 'management project is not required' + end + + context 'cluster already has a management project' do + let(:cluster) { create(:cluster, :management_project) } + + include_examples 'management project is not required' + end + + shared_examples 'creates a management project' do + let(:project_params) do + { + name: "#{cluster.name} Cluster Management", + description: 'This project is automatically generated and will be used to manage your Kubernetes cluster. [More information](/help/user/clusters/management_project)', + namespace_id: namespace&.id, + visibility_level: Gitlab::VisibilityLevel::PRIVATE + } + end + + it 'creates a management project' do + expect(Projects::CreateService).to receive(:new) + .with(current_user, project_params) + .and_call_original + + subject + + management_project = cluster.management_project + + expect(management_project).to be_present + expect(management_project).to be_private + expect(management_project.name).to eq "#{cluster.name} Cluster Management" + expect(management_project.namespace).to eq namespace + end + end + + context 'project cluster' do + let(:cluster) { create(:cluster, projects: [project]) } + let(:project) { create(:project, namespace: current_user.namespace) } + let(:namespace) { project.namespace } + + include_examples 'creates a management project' + end + + context 'group cluster' do + let(:cluster) { create(:cluster, :group, user: current_user) } + let(:namespace) { cluster.group } + + before do + namespace.add_user(current_user, Gitlab::Access::MAINTAINER) + end + + include_examples 'creates a management project' + end + + context 'instance cluster' do + let(:cluster) { create(:cluster, :instance, user: current_user) } + let(:namespace) { create(:group) } + + before do + stub_application_setting(instance_administrators_group: namespace) + + namespace.add_user(current_user, Gitlab::Access::MAINTAINER) + end + + include_examples 'creates a management project' + end + + describe 'error handling' do + let(:project) { cluster.project } + + before do + allow(Projects::CreateService).to receive(:new) + .and_return(double(execute: project)) + end + + context 'project is invalid' do + let(:errors) { double(full_messages: ["Error message"]) } + let(:project) { instance_double(Project, errors: errors) } + + it { expect { subject }.to raise_error(described_class::CreateError, /Failed to create project/) } + end + + context 'instance administrators group is missing' do + let(:cluster) { create(:cluster, :instance) } + + it { expect { subject }.to raise_error(described_class::CreateError, /Instance administrators group not found/) } + end + + context 'cluster is invalid' do + before do + allow(cluster).to receive(:update).and_return(false) + end + + it { expect { subject }.to raise_error(described_class::CreateError, /Failed to update cluster/) } + end + + context 'unknown cluster type' do + before do + allow(cluster).to receive(:cluster_type).and_return("unknown_type") + end + + it { expect { subject }.to raise_error(NotImplementedError) } + end + end +end |