Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-03-24 06:09:28 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-03-24 06:09:28 +0300
commitbe2f4c5788975597dd7be1c8a3525549770c1216 (patch)
tree083ed0d7e29e26d479c00e00d9cb89d74ebbb0ef /spec
parent2711c26beaca6c3a5a3be4b65e01557faf0185b6 (diff)
Add latest changes from gitlab-org/gitlab@master
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.js47
-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.js53
-rw-r--r--spec/javascripts/boards/components/board_form_spec.js56
-rw-r--r--spec/javascripts/boards/mock_data.js54
-rw-r--r--spec/lib/gitlab/kubernetes/helm/install_command_spec.rb14
-rw-r--r--spec/services/clusters/management/create_project_service_spec.rb126
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