summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-04-28 21:09:35 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-04-28 21:09:35 +0000
commitabe11a6a2c04112d0b7d6d4facfd0c8370f51831 (patch)
treeb0c9e9e019417e7b438bf24c6a4a28acfc0fd95b /spec
parent95e18e32833de71b46d73ead66c8f13e261af3f4 (diff)
downloadgitlab-ce-abe11a6a2c04112d0b7d6d4facfd0c8370f51831.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/factories/remote_mirrors.rb5
-rw-r--r--spec/frontend/helpers/web_worker_mock.js10
-rw-r--r--spec/frontend/ide/components/activity_bar_spec.js (renamed from spec/javascripts/ide/components/activity_bar_spec.js)2
-rw-r--r--spec/frontend/ide/components/file_templates/bar_spec.js (renamed from spec/javascripts/ide/components/file_templates/bar_spec.js)10
-rw-r--r--spec/frontend/ide/components/ide_side_bar_spec.js (renamed from spec/javascripts/ide/components/ide_side_bar_spec.js)2
-rw-r--r--spec/frontend/ide/components/ide_spec.js (renamed from spec/javascripts/ide/components/ide_spec.js)2
-rw-r--r--spec/frontend/ide/components/ide_tree_list_spec.js (renamed from spec/javascripts/ide/components/ide_tree_list_spec.js)4
-rw-r--r--spec/frontend/ide/components/ide_tree_spec.js (renamed from spec/javascripts/ide/components/ide_tree_spec.js)0
-rw-r--r--spec/frontend/ide/components/jobs/detail/description_spec.js (renamed from spec/javascripts/ide/components/jobs/detail/description_spec.js)0
-rw-r--r--spec/frontend/ide/components/jobs/item_spec.js (renamed from spec/javascripts/ide/components/jobs/item_spec.js)0
-rw-r--r--spec/frontend/ide/components/nav_dropdown_button_spec.js (renamed from spec/javascripts/ide/components/nav_dropdown_button_spec.js)4
-rw-r--r--spec/frontend/ide/components/nav_dropdown_spec.js102
-rw-r--r--spec/frontend/ide/components/new_dropdown/button_spec.js (renamed from spec/javascripts/ide/components/new_dropdown/button_spec.js)4
-rw-r--r--spec/frontend/ide/components/new_dropdown/index_spec.js (renamed from spec/javascripts/ide/components/new_dropdown/index_spec.js)10
-rw-r--r--spec/frontend/ide/components/new_dropdown/modal_spec.js (renamed from spec/javascripts/ide/components/new_dropdown/modal_spec.js)74
-rw-r--r--spec/frontend/ide/components/repo_tab_spec.js (renamed from spec/javascripts/ide/components/repo_tab_spec.js)8
-rw-r--r--spec/frontend/ide/components/repo_tabs_spec.js (renamed from spec/javascripts/ide/components/repo_tabs_spec.js)0
-rw-r--r--spec/frontend/ide/components/shared/tokened_input_spec.js (renamed from spec/javascripts/ide/components/shared/tokened_input_spec.js)6
-rw-r--r--spec/frontend/ide/lib/common/model_manager_spec.js (renamed from spec/javascripts/ide/lib/common/model_manager_spec.js)14
-rw-r--r--spec/frontend/ide/lib/common/model_spec.js (renamed from spec/javascripts/ide/lib/common/model_spec.js)18
-rw-r--r--spec/frontend/ide/lib/decorations/controller_spec.js (renamed from spec/javascripts/ide/lib/decorations/controller_spec.js)8
-rw-r--r--spec/frontend/ide/lib/diff/controller_spec.js (renamed from spec/javascripts/ide/lib/diff/controller_spec.js)26
-rw-r--r--spec/frontend/ide/lib/editor_spec.js (renamed from spec/javascripts/ide/lib/editor_spec.js)61
-rw-r--r--spec/frontend/mocks/ce/diffs/workers/tree_worker.js9
-rw-r--r--spec/frontend/mocks/ce/ide/lib/diff/diff_worker.js1
-rw-r--r--spec/javascripts/ide/components/nav_dropdown_spec.js80
-rw-r--r--spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb70
-rw-r--r--spec/migrations/backfill_snippet_repositories_spec.rb44
-rw-r--r--spec/models/remote_mirror_spec.rb52
-rw-r--r--spec/requests/api/graphql/mutations/snippets/destroy_spec.rb13
-rw-r--r--spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb6
-rw-r--r--spec/requests/api/graphql/mutations/snippets/update_spec.rb25
-rw-r--r--spec/services/projects/update_remote_mirror_service_spec.rb37
-rw-r--r--spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb10
34 files changed, 448 insertions, 269 deletions
diff --git a/spec/factories/remote_mirrors.rb b/spec/factories/remote_mirrors.rb
index 124c0510cab..aa0ace30d90 100644
--- a/spec/factories/remote_mirrors.rb
+++ b/spec/factories/remote_mirrors.rb
@@ -4,5 +4,10 @@ FactoryBot.define do
factory :remote_mirror, class: 'RemoteMirror' do
association :project, :repository
url { "http://foo:bar@test.com" }
+
+ trait :ssh do
+ url { 'ssh://git@test.com:foo/bar.git' }
+ auth_method { 'ssh_public_key' }
+ end
end
end
diff --git a/spec/frontend/helpers/web_worker_mock.js b/spec/frontend/helpers/web_worker_mock.js
new file mode 100644
index 00000000000..2b4a391e1d2
--- /dev/null
+++ b/spec/frontend/helpers/web_worker_mock.js
@@ -0,0 +1,10 @@
+/* eslint-disable class-methods-use-this */
+export default class WebWorkerMock {
+ addEventListener() {}
+
+ removeEventListener() {}
+
+ terminate() {}
+
+ postMessage() {}
+}
diff --git a/spec/javascripts/ide/components/activity_bar_spec.js b/spec/frontend/ide/components/activity_bar_spec.js
index 823ca29dab9..8b3853d4535 100644
--- a/spec/javascripts/ide/components/activity_bar_spec.js
+++ b/spec/frontend/ide/components/activity_bar_spec.js
@@ -26,7 +26,7 @@ describe('IDE activity bar', () => {
describe('updateActivityBarView', () => {
beforeEach(() => {
- spyOn(vm, 'updateActivityBarView');
+ jest.spyOn(vm, 'updateActivityBarView').mockImplementation(() => {});
vm.$mount();
});
diff --git a/spec/javascripts/ide/components/file_templates/bar_spec.js b/spec/frontend/ide/components/file_templates/bar_spec.js
index 5399ada94ae..21dbe18a223 100644
--- a/spec/javascripts/ide/components/file_templates/bar_spec.js
+++ b/spec/frontend/ide/components/file_templates/bar_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import { createStore } from '~/ide/stores';
import Bar from '~/ide/components/file_templates/bar.vue';
import { resetStore, file } from '../../helpers';
@@ -35,7 +35,7 @@ describe('IDE file templates bar component', () => {
});
it('calls setSelectedTemplateType when clicking item', () => {
- spyOn(vm, 'setSelectedTemplateType').and.stub();
+ jest.spyOn(vm, 'setSelectedTemplateType').mockImplementation();
vm.$el.querySelector('.dropdown-content button').click();
@@ -66,7 +66,7 @@ describe('IDE file templates bar component', () => {
});
it('calls fetchTemplate on click', () => {
- spyOn(vm, 'fetchTemplate').and.stub();
+ jest.spyOn(vm, 'fetchTemplate').mockImplementation();
vm.$el
.querySelectorAll('.dropdown-content')[1]
@@ -90,7 +90,7 @@ describe('IDE file templates bar component', () => {
});
it('calls undoFileTemplate when clicking undo button', () => {
- spyOn(vm, 'undoFileTemplate').and.stub();
+ jest.spyOn(vm, 'undoFileTemplate').mockImplementation();
vm.$el.querySelector('.btn-default').click();
@@ -100,7 +100,7 @@ describe('IDE file templates bar component', () => {
it('calls setSelectedTemplateType if activeFile name matches a template', done => {
const fileName = '.gitlab-ci.yml';
- spyOn(vm, 'setSelectedTemplateType');
+ jest.spyOn(vm, 'setSelectedTemplateType').mockImplementation(() => {});
vm.$store.state.openFiles[0].name = fileName;
vm.setInitialType();
diff --git a/spec/javascripts/ide/components/ide_side_bar_spec.js b/spec/frontend/ide/components/ide_side_bar_spec.js
index 28f127a61c0..65cad2e7eb0 100644
--- a/spec/javascripts/ide/components/ide_side_bar_spec.js
+++ b/spec/frontend/ide/components/ide_side_bar_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import store from '~/ide/stores';
import ideSidebar from '~/ide/components/ide_side_bar.vue';
import { leftSidebarViews } from '~/ide/constants';
diff --git a/spec/javascripts/ide/components/ide_spec.js b/spec/frontend/ide/components/ide_spec.js
index 4241b994cba..ad2b3c8c01a 100644
--- a/spec/javascripts/ide/components/ide_spec.js
+++ b/spec/frontend/ide/components/ide_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import store from '~/ide/stores';
import ide from '~/ide/components/ide.vue';
import { file, resetStore } from '../helpers';
diff --git a/spec/javascripts/ide/components/ide_tree_list_spec.js b/spec/frontend/ide/components/ide_tree_list_spec.js
index f63007c7dd2..6694ac497fb 100644
--- a/spec/javascripts/ide/components/ide_tree_list_spec.js
+++ b/spec/frontend/ide/components/ide_tree_list_spec.js
@@ -35,7 +35,7 @@ describe('IDE tree list', () => {
beforeEach(() => {
bootstrapWithTree();
- spyOn(vm, 'updateViewer').and.callThrough();
+ jest.spyOn(vm, 'updateViewer');
vm.$mount();
});
@@ -64,7 +64,7 @@ describe('IDE tree list', () => {
beforeEach(() => {
bootstrapWithTree(emptyBranchTree);
- spyOn(vm, 'updateViewer').and.callThrough();
+ jest.spyOn(vm, 'updateViewer');
vm.$mount();
});
diff --git a/spec/javascripts/ide/components/ide_tree_spec.js b/spec/frontend/ide/components/ide_tree_spec.js
index 97a0a2432f1..97a0a2432f1 100644
--- a/spec/javascripts/ide/components/ide_tree_spec.js
+++ b/spec/frontend/ide/components/ide_tree_spec.js
diff --git a/spec/javascripts/ide/components/jobs/detail/description_spec.js b/spec/frontend/ide/components/jobs/detail/description_spec.js
index babae00d2f7..babae00d2f7 100644
--- a/spec/javascripts/ide/components/jobs/detail/description_spec.js
+++ b/spec/frontend/ide/components/jobs/detail/description_spec.js
diff --git a/spec/javascripts/ide/components/jobs/item_spec.js b/spec/frontend/ide/components/jobs/item_spec.js
index 2f97d39e98e..2f97d39e98e 100644
--- a/spec/javascripts/ide/components/jobs/item_spec.js
+++ b/spec/frontend/ide/components/jobs/item_spec.js
diff --git a/spec/javascripts/ide/components/nav_dropdown_button_spec.js b/spec/frontend/ide/components/nav_dropdown_button_spec.js
index bbaf97164ea..2aa3992a6d8 100644
--- a/spec/javascripts/ide/components/nav_dropdown_button_spec.js
+++ b/spec/frontend/ide/components/nav_dropdown_button_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import { trimText } from 'spec/helpers/text_helper';
-import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { trimText } from 'helpers/text_helper';
+import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import NavDropdownButton from '~/ide/components/nav_dropdown_button.vue';
import { createStore } from '~/ide/stores';
diff --git a/spec/frontend/ide/components/nav_dropdown_spec.js b/spec/frontend/ide/components/nav_dropdown_spec.js
new file mode 100644
index 00000000000..ce123d925c8
--- /dev/null
+++ b/spec/frontend/ide/components/nav_dropdown_spec.js
@@ -0,0 +1,102 @@
+import $ from 'jquery';
+import { mount } from '@vue/test-utils';
+import { createStore } from '~/ide/stores';
+import NavDropdown from '~/ide/components/nav_dropdown.vue';
+import { PERMISSION_READ_MR } from '~/ide/constants';
+
+const TEST_PROJECT_ID = 'lorem-ipsum';
+
+describe('IDE NavDropdown', () => {
+ let store;
+ let wrapper;
+
+ beforeEach(() => {
+ store = createStore();
+ Object.assign(store.state, {
+ currentProjectId: TEST_PROJECT_ID,
+ currentBranchId: 'master',
+ projects: {
+ [TEST_PROJECT_ID]: {
+ userPermissions: {
+ [PERMISSION_READ_MR]: true,
+ },
+ branches: {
+ master: { id: 'master' },
+ },
+ },
+ },
+ });
+ jest.spyOn(store, 'dispatch').mockImplementation(() => {});
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const createComponent = () => {
+ wrapper = mount(NavDropdown, {
+ store,
+ });
+ };
+
+ const findIcon = name => wrapper.find(`.ic-${name}`);
+ const findMRIcon = () => findIcon('merge-request');
+ const findNavForm = () => wrapper.find('.ide-nav-form');
+ const showDropdown = () => {
+ $(wrapper.vm.$el).trigger('show.bs.dropdown');
+ };
+ const hideDropdown = () => {
+ $(wrapper.vm.$el).trigger('hide.bs.dropdown');
+ };
+
+ describe('default', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders nothing initially', () => {
+ expect(findNavForm().exists()).toBe(false);
+ });
+
+ it('renders nav form when show.bs.dropdown', done => {
+ showDropdown();
+
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ expect(findNavForm().exists()).toBe(true);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('destroys nav form when closed', done => {
+ showDropdown();
+ hideDropdown();
+
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ expect(findNavForm().exists()).toBe(false);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('renders merge request icon', () => {
+ expect(findMRIcon().exists()).toBe(true);
+ });
+ });
+
+ describe('when user cannot read merge requests', () => {
+ beforeEach(() => {
+ store.state.projects[TEST_PROJECT_ID].userPermissions = {};
+
+ createComponent();
+ });
+
+ it('does not render merge requests', () => {
+ expect(findMRIcon().exists()).toBe(false);
+ });
+ });
+});
diff --git a/spec/javascripts/ide/components/new_dropdown/button_spec.js b/spec/frontend/ide/components/new_dropdown/button_spec.js
index 6a326b5bd92..3c611b7de8f 100644
--- a/spec/javascripts/ide/components/new_dropdown/button_spec.js
+++ b/spec/frontend/ide/components/new_dropdown/button_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import mountComponent from 'helpers/vue_mount_component_helper';
import Button from '~/ide/components/new_dropdown/button.vue';
describe('IDE new entry dropdown button component', () => {
@@ -16,7 +16,7 @@ describe('IDE new entry dropdown button component', () => {
icon: 'doc-new',
});
- spyOn(vm, '$emit');
+ jest.spyOn(vm, '$emit').mockImplementation(() => {});
});
afterEach(() => {
diff --git a/spec/javascripts/ide/components/new_dropdown/index_spec.js b/spec/frontend/ide/components/new_dropdown/index_spec.js
index 03afe997fed..f4fecb68b64 100644
--- a/spec/javascripts/ide/components/new_dropdown/index_spec.js
+++ b/spec/frontend/ide/components/new_dropdown/index_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import store from '~/ide/stores';
import newDropdown from '~/ide/components/new_dropdown/index.vue';
import { resetStore } from '../../helpers';
@@ -23,7 +23,7 @@ describe('new dropdown component', () => {
tree: [],
};
- spyOn(vm, 'openNewEntryModal');
+ jest.spyOn(vm, 'openNewEntryModal').mockImplementation(() => {});
vm.$mount();
});
@@ -58,11 +58,11 @@ describe('new dropdown component', () => {
describe('isOpen', () => {
it('scrolls dropdown into view', done => {
- spyOn(vm.$refs.dropdownMenu, 'scrollIntoView');
+ jest.spyOn(vm.$refs.dropdownMenu, 'scrollIntoView').mockImplementation(() => {});
vm.isOpen = true;
- setTimeout(() => {
+ setImmediate(() => {
expect(vm.$refs.dropdownMenu.scrollIntoView).toHaveBeenCalledWith({
block: 'nearest',
});
@@ -74,7 +74,7 @@ describe('new dropdown component', () => {
describe('delete entry', () => {
it('calls delete action', () => {
- spyOn(vm, 'deleteEntry');
+ jest.spyOn(vm, 'deleteEntry').mockImplementation(() => {});
vm.$el.querySelectorAll('.dropdown-menu button')[4].click();
diff --git a/spec/javascripts/ide/components/new_dropdown/modal_spec.js b/spec/frontend/ide/components/new_dropdown/modal_spec.js
index 0ea767e087d..2f10bf787b3 100644
--- a/spec/javascripts/ide/components/new_dropdown/modal_spec.js
+++ b/spec/frontend/ide/components/new_dropdown/modal_spec.js
@@ -1,7 +1,10 @@
import Vue from 'vue';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import { createStore } from '~/ide/stores';
import modal from '~/ide/components/new_dropdown/modal.vue';
+import createFlash from '~/flash';
+
+jest.mock('~/flash');
describe('new file modal component', () => {
const Component = Vue.extend(modal);
@@ -11,47 +14,45 @@ describe('new file modal component', () => {
vm.$destroy();
});
- ['tree', 'blob'].forEach(type => {
- describe(type, () => {
- beforeEach(() => {
- const store = createStore();
- store.state.entryModal = {
- type,
+ describe.each(['tree', 'blob'])('%s', type => {
+ beforeEach(() => {
+ const store = createStore();
+ store.state.entryModal = {
+ type,
+ path: '',
+ entry: {
path: '',
- entry: {
- path: '',
- },
- };
+ },
+ };
- vm = createComponentWithStore(Component, store).$mount();
+ vm = createComponentWithStore(Component, store).$mount();
- vm.name = 'testing';
- });
+ vm.name = 'testing';
+ });
- it(`sets modal title as ${type}`, () => {
- const title = type === 'tree' ? 'directory' : 'file';
+ it(`sets modal title as ${type}`, () => {
+ const title = type === 'tree' ? 'directory' : 'file';
- expect(vm.$el.querySelector('.modal-title').textContent.trim()).toBe(`Create new ${title}`);
- });
+ expect(vm.$el.querySelector('.modal-title').textContent.trim()).toBe(`Create new ${title}`);
+ });
- it(`sets button label as ${type}`, () => {
- const title = type === 'tree' ? 'directory' : 'file';
+ it(`sets button label as ${type}`, () => {
+ const title = type === 'tree' ? 'directory' : 'file';
- expect(vm.$el.querySelector('.btn-success').textContent.trim()).toBe(`Create ${title}`);
- });
+ expect(vm.$el.querySelector('.btn-success').textContent.trim()).toBe(`Create ${title}`);
+ });
- it(`sets form label as ${type}`, () => {
- expect(vm.$el.querySelector('.label-bold').textContent.trim()).toBe('Name');
- });
+ it(`sets form label as ${type}`, () => {
+ expect(vm.$el.querySelector('.label-bold').textContent.trim()).toBe('Name');
+ });
- it(`${type === 'tree' ? 'does not show' : 'shows'} file templates`, () => {
- const templateFilesEl = vm.$el.querySelector('.file-templates');
- if (type === 'tree') {
- expect(templateFilesEl).toBeNull();
- } else {
- expect(templateFilesEl instanceof Element).toBeTruthy();
- }
- });
+ it(`${type === 'tree' ? 'does not show' : 'shows'} file templates`, () => {
+ const templateFilesEl = vm.$el.querySelector('.file-templates');
+ if (type === 'tree') {
+ expect(templateFilesEl).toBeNull();
+ } else {
+ expect(templateFilesEl instanceof Element).toBeTruthy();
+ }
});
});
@@ -131,16 +132,15 @@ describe('new file modal component', () => {
};
vm = createComponentWithStore(Component, store).$mount();
- const flashSpy = spyOnDependency(modal, 'flash');
- expect(flashSpy).not.toHaveBeenCalled();
+ expect(createFlash).not.toHaveBeenCalled();
vm.submitForm();
- expect(flashSpy).toHaveBeenCalledWith(
+ expect(createFlash).toHaveBeenCalledWith(
'The name "test-path/test" is already taken in this directory.',
'alert',
- jasmine.anything(),
+ expect.anything(),
null,
false,
true,
diff --git a/spec/javascripts/ide/components/repo_tab_spec.js b/spec/frontend/ide/components/repo_tab_spec.js
index 3b52f279bf2..82ea73ffbb1 100644
--- a/spec/javascripts/ide/components/repo_tab_spec.js
+++ b/spec/frontend/ide/components/repo_tab_spec.js
@@ -17,7 +17,7 @@ describe('RepoTab', () => {
}
beforeEach(() => {
- spyOn(router, 'push');
+ jest.spyOn(router, 'push').mockImplementation(() => {});
});
afterEach(() => {
@@ -47,7 +47,7 @@ describe('RepoTab', () => {
},
});
- spyOn(vm, 'openPendingTab');
+ jest.spyOn(vm, 'openPendingTab').mockImplementation(() => {});
vm.$el.click();
@@ -63,7 +63,7 @@ describe('RepoTab', () => {
tab: file(),
});
- spyOn(vm, 'clickFile');
+ jest.spyOn(vm, 'clickFile').mockImplementation(() => {});
vm.$el.click();
@@ -75,7 +75,7 @@ describe('RepoTab', () => {
tab: file(),
});
- spyOn(vm, 'closeFile');
+ jest.spyOn(vm, 'closeFile').mockImplementation(() => {});
vm.$el.querySelector('.multi-file-tab-close').click();
diff --git a/spec/javascripts/ide/components/repo_tabs_spec.js b/spec/frontend/ide/components/repo_tabs_spec.js
index 583f71e6121..583f71e6121 100644
--- a/spec/javascripts/ide/components/repo_tabs_spec.js
+++ b/spec/frontend/ide/components/repo_tabs_spec.js
diff --git a/spec/javascripts/ide/components/shared/tokened_input_spec.js b/spec/frontend/ide/components/shared/tokened_input_spec.js
index 885fd976655..e687216bd06 100644
--- a/spec/javascripts/ide/components/shared/tokened_input_spec.js
+++ b/spec/frontend/ide/components/shared/tokened_input_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import mountComponent from 'helpers/vue_mount_component_helper';
import TokenedInput from '~/ide/components/shared/tokened_input.vue';
const TEST_PLACEHOLDER = 'Searching in test';
@@ -36,7 +36,7 @@ describe('IDE shared/TokenedInput', () => {
value: TEST_VALUE,
});
- spyOn(vm, '$emit');
+ jest.spyOn(vm, '$emit').mockImplementation(() => {});
});
afterEach(() => {
@@ -72,7 +72,7 @@ describe('IDE shared/TokenedInput', () => {
});
it('when input triggers backspace event, it calls "onBackspace"', () => {
- spyOn(vm, 'onBackspace');
+ jest.spyOn(vm, 'onBackspace').mockImplementation(() => {});
vm.$refs.input.dispatchEvent(createBackspaceEvent());
vm.$refs.input.dispatchEvent(createBackspaceEvent());
diff --git a/spec/javascripts/ide/lib/common/model_manager_spec.js b/spec/frontend/ide/lib/common/model_manager_spec.js
index 38ffa317e8e..08e4ab0f113 100644
--- a/spec/javascripts/ide/lib/common/model_manager_spec.js
+++ b/spec/frontend/ide/lib/common/model_manager_spec.js
@@ -28,7 +28,7 @@ describe('Multi-file editor library model manager', () => {
});
it('adds model into disposable', () => {
- spyOn(instance.disposable, 'add').and.callThrough();
+ jest.spyOn(instance.disposable, 'add');
instance.addModel(file());
@@ -36,7 +36,7 @@ describe('Multi-file editor library model manager', () => {
});
it('returns cached model', () => {
- spyOn(instance.models, 'get').and.callThrough();
+ jest.spyOn(instance.models, 'get');
instance.addModel(file());
instance.addModel(file());
@@ -46,13 +46,13 @@ describe('Multi-file editor library model manager', () => {
it('adds eventHub listener', () => {
const f = file();
- spyOn(eventHub, '$on').and.callThrough();
+ jest.spyOn(eventHub, '$on');
instance.addModel(f);
expect(eventHub.$on).toHaveBeenCalledWith(
`editor.update.model.dispose.${f.key}`,
- jasmine.anything(),
+ expect.anything(),
);
});
});
@@ -95,13 +95,13 @@ describe('Multi-file editor library model manager', () => {
});
it('removes eventHub listener', () => {
- spyOn(eventHub, '$off').and.callThrough();
+ jest.spyOn(eventHub, '$off');
instance.removeCachedModel(f);
expect(eventHub.$off).toHaveBeenCalledWith(
`editor.update.model.dispose.${f.key}`,
- jasmine.anything(),
+ expect.anything(),
);
});
});
@@ -116,7 +116,7 @@ describe('Multi-file editor library model manager', () => {
});
it('calls disposable dispose', () => {
- spyOn(instance.disposable, 'dispose').and.callThrough();
+ jest.spyOn(instance.disposable, 'dispose');
instance.dispose();
diff --git a/spec/javascripts/ide/lib/common/model_spec.js b/spec/frontend/ide/lib/common/model_spec.js
index f096e06f43c..2ef2f0da6da 100644
--- a/spec/javascripts/ide/lib/common/model_spec.js
+++ b/spec/frontend/ide/lib/common/model_spec.js
@@ -6,7 +6,7 @@ describe('Multi-file editor library model', () => {
let model;
beforeEach(() => {
- spyOn(eventHub, '$on').and.callThrough();
+ jest.spyOn(eventHub, '$on');
const f = file('path');
f.mrChange = { diff: 'ABC' };
@@ -44,7 +44,7 @@ describe('Multi-file editor library model', () => {
it('adds eventHub listener', () => {
expect(eventHub.$on).toHaveBeenCalledWith(
`editor.update.model.dispose.${model.file.key}`,
- jasmine.anything(),
+ expect.anything(),
);
});
@@ -82,13 +82,13 @@ describe('Multi-file editor library model', () => {
describe('onChange', () => {
it('calls callback on change', done => {
- const spy = jasmine.createSpy();
+ const spy = jest.fn();
model.onChange(spy);
model.getModel().setValue('123');
- setTimeout(() => {
- expect(spy).toHaveBeenCalledWith(model, jasmine.anything());
+ setImmediate(() => {
+ expect(spy).toHaveBeenCalledWith(model, expect.anything());
done();
});
});
@@ -96,7 +96,7 @@ describe('Multi-file editor library model', () => {
describe('dispose', () => {
it('calls disposable dispose', () => {
- spyOn(model.disposable, 'dispose').and.callThrough();
+ jest.spyOn(model.disposable, 'dispose');
model.dispose();
@@ -114,18 +114,18 @@ describe('Multi-file editor library model', () => {
});
it('removes eventHub listener', () => {
- spyOn(eventHub, '$off').and.callThrough();
+ jest.spyOn(eventHub, '$off');
model.dispose();
expect(eventHub.$off).toHaveBeenCalledWith(
`editor.update.model.dispose.${model.file.key}`,
- jasmine.anything(),
+ expect.anything(),
);
});
it('calls onDispose callback', () => {
- const disposeSpy = jasmine.createSpy();
+ const disposeSpy = jest.fn();
model.onDispose(disposeSpy);
diff --git a/spec/javascripts/ide/lib/decorations/controller_spec.js b/spec/frontend/ide/lib/decorations/controller_spec.js
index 4118774cca3..4556fc9d646 100644
--- a/spec/javascripts/ide/lib/decorations/controller_spec.js
+++ b/spec/frontend/ide/lib/decorations/controller_spec.js
@@ -60,7 +60,7 @@ describe('Multi-file editor library decorations controller', () => {
});
it('calls decorate method', () => {
- spyOn(controller, 'decorate');
+ jest.spyOn(controller, 'decorate').mockImplementation(() => {});
controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
@@ -70,7 +70,7 @@ describe('Multi-file editor library decorations controller', () => {
describe('decorate', () => {
it('sets decorations on editor instance', () => {
- spyOn(controller.editor.instance, 'deltaDecorations');
+ jest.spyOn(controller.editor.instance, 'deltaDecorations').mockImplementation(() => {});
controller.decorate(model);
@@ -78,7 +78,7 @@ describe('Multi-file editor library decorations controller', () => {
});
it('caches decorations', () => {
- spyOn(controller.editor.instance, 'deltaDecorations').and.returnValue([]);
+ jest.spyOn(controller.editor.instance, 'deltaDecorations').mockReturnValue([]);
controller.decorate(model);
@@ -86,7 +86,7 @@ describe('Multi-file editor library decorations controller', () => {
});
it('caches decorations by model URL', () => {
- spyOn(controller.editor.instance, 'deltaDecorations').and.returnValue([]);
+ jest.spyOn(controller.editor.instance, 'deltaDecorations').mockReturnValue([]);
controller.decorate(model);
diff --git a/spec/javascripts/ide/lib/diff/controller_spec.js b/spec/frontend/ide/lib/diff/controller_spec.js
index 90ebb95b687..0b33a4c6ad6 100644
--- a/spec/javascripts/ide/lib/diff/controller_spec.js
+++ b/spec/frontend/ide/lib/diff/controller_spec.js
@@ -75,7 +75,7 @@ describe('Multi-file editor library dirty diff controller', () => {
describe('attachModel', () => {
it('adds change event callback', () => {
- spyOn(model, 'onChange');
+ jest.spyOn(model, 'onChange').mockImplementation(() => {});
controller.attachModel(model);
@@ -83,7 +83,7 @@ describe('Multi-file editor library dirty diff controller', () => {
});
it('adds dispose event callback', () => {
- spyOn(model, 'onDispose');
+ jest.spyOn(model, 'onDispose').mockImplementation(() => {});
controller.attachModel(model);
@@ -91,7 +91,7 @@ describe('Multi-file editor library dirty diff controller', () => {
});
it('calls throttledComputeDiff on change', () => {
- spyOn(controller, 'throttledComputeDiff');
+ jest.spyOn(controller, 'throttledComputeDiff').mockImplementation(() => {});
controller.attachModel(model);
@@ -109,7 +109,7 @@ describe('Multi-file editor library dirty diff controller', () => {
describe('computeDiff', () => {
it('posts to worker', () => {
- spyOn(controller.dirtyDiffWorker, 'postMessage');
+ jest.spyOn(controller.dirtyDiffWorker, 'postMessage').mockImplementation(() => {});
controller.computeDiff(model);
@@ -123,7 +123,7 @@ describe('Multi-file editor library dirty diff controller', () => {
describe('reDecorate', () => {
it('calls computeDiff when no decorations are cached', () => {
- spyOn(controller, 'computeDiff');
+ jest.spyOn(controller, 'computeDiff').mockImplementation(() => {});
controller.reDecorate(model);
@@ -131,7 +131,7 @@ describe('Multi-file editor library dirty diff controller', () => {
});
it('calls decorate when decorations are cached', () => {
- spyOn(controller.decorationsController, 'decorate');
+ jest.spyOn(controller.decorationsController, 'decorate').mockImplementation(() => {});
controller.decorationsController.decorations.set(model.url, 'test');
@@ -143,19 +143,19 @@ describe('Multi-file editor library dirty diff controller', () => {
describe('decorate', () => {
it('adds decorations into decorations controller', () => {
- spyOn(controller.decorationsController, 'addDecorations');
+ jest.spyOn(controller.decorationsController, 'addDecorations').mockImplementation(() => {});
controller.decorate({ data: { changes: [], path: model.path } });
expect(controller.decorationsController.addDecorations).toHaveBeenCalledWith(
model,
'dirtyDiff',
- jasmine.anything(),
+ expect.anything(),
);
});
it('adds decorations into editor', () => {
- const spy = spyOn(controller.decorationsController.editor.instance, 'deltaDecorations');
+ const spy = jest.spyOn(controller.decorationsController.editor.instance, 'deltaDecorations');
controller.decorate({
data: { changes: computeDiff('123', '1234'), path: model.path },
@@ -178,7 +178,7 @@ describe('Multi-file editor library dirty diff controller', () => {
describe('dispose', () => {
it('calls disposable dispose', () => {
- spyOn(controller.disposable, 'dispose').and.callThrough();
+ jest.spyOn(controller.disposable, 'dispose');
controller.dispose();
@@ -186,7 +186,7 @@ describe('Multi-file editor library dirty diff controller', () => {
});
it('terminates worker', () => {
- spyOn(controller.dirtyDiffWorker, 'terminate').and.callThrough();
+ jest.spyOn(controller.dirtyDiffWorker, 'terminate');
controller.dispose();
@@ -194,13 +194,13 @@ describe('Multi-file editor library dirty diff controller', () => {
});
it('removes worker event listener', () => {
- spyOn(controller.dirtyDiffWorker, 'removeEventListener').and.callThrough();
+ jest.spyOn(controller.dirtyDiffWorker, 'removeEventListener');
controller.dispose();
expect(controller.dirtyDiffWorker.removeEventListener).toHaveBeenCalledWith(
'message',
- jasmine.anything(),
+ expect.anything(),
);
});
diff --git a/spec/javascripts/ide/lib/editor_spec.js b/spec/frontend/ide/lib/editor_spec.js
index 556bd45d3a5..78e7bf5b58a 100644
--- a/spec/javascripts/ide/lib/editor_spec.js
+++ b/spec/frontend/ide/lib/editor_spec.js
@@ -1,5 +1,6 @@
import { editor as monacoEditor } from 'monaco-editor';
import Editor from '~/ide/lib/editor';
+import { defaultEditorOptions } from '~/ide/lib/editor_options';
import { file } from '../helpers';
describe('Multi-file editor library', () => {
@@ -7,6 +8,14 @@ describe('Multi-file editor library', () => {
let el;
let holder;
+ const setNodeOffsetWidth = val => {
+ Object.defineProperty(instance.instance.getDomNode(), 'offsetWidth', {
+ get() {
+ return val;
+ },
+ });
+ };
+
beforeEach(() => {
el = document.createElement('div');
holder = document.createElement('div');
@@ -18,7 +27,9 @@ describe('Multi-file editor library', () => {
});
afterEach(() => {
+ instance.modelManager.dispose();
instance.dispose();
+ Editor.editorInstance = null;
el.remove();
});
@@ -33,7 +44,7 @@ describe('Multi-file editor library', () => {
describe('createInstance', () => {
it('creates editor instance', () => {
- spyOn(monacoEditor, 'create').and.callThrough();
+ jest.spyOn(monacoEditor, 'create');
instance.createInstance(holder);
@@ -55,33 +66,25 @@ describe('Multi-file editor library', () => {
describe('createDiffInstance', () => {
it('creates editor instance', () => {
- spyOn(monacoEditor, 'createDiffEditor').and.callThrough();
+ jest.spyOn(monacoEditor, 'createDiffEditor');
instance.createDiffInstance(holder);
expect(monacoEditor.createDiffEditor).toHaveBeenCalledWith(holder, {
- model: null,
- contextmenu: true,
- minimap: {
- enabled: false,
- },
- readOnly: true,
- scrollBeyondLastLine: false,
- renderWhitespace: 'none',
+ ...defaultEditorOptions,
quickSuggestions: false,
occurrencesHighlight: false,
- wordWrap: 'on',
- renderSideBySide: true,
+ renderSideBySide: false,
+ readOnly: true,
renderLineHighlight: 'all',
hideCursorInOverviewRuler: false,
- theme: 'vs white',
});
});
});
describe('createModel', () => {
it('calls model manager addModel', () => {
- spyOn(instance.modelManager, 'addModel');
+ jest.spyOn(instance.modelManager, 'addModel').mockImplementation(() => {});
instance.createModel('FILE');
@@ -105,7 +108,7 @@ describe('Multi-file editor library', () => {
});
it('attaches the model to the current instance', () => {
- spyOn(instance.instance, 'setModel');
+ jest.spyOn(instance.instance, 'setModel').mockImplementation(() => {});
instance.attachModel(model);
@@ -113,8 +116,8 @@ describe('Multi-file editor library', () => {
});
it('sets original & modified when diff editor', () => {
- spyOn(instance.instance, 'getEditorType').and.returnValue('vs.editor.IDiffEditor');
- spyOn(instance.instance, 'setModel');
+ jest.spyOn(instance.instance, 'getEditorType').mockReturnValue('vs.editor.IDiffEditor');
+ jest.spyOn(instance.instance, 'setModel').mockImplementation(() => {});
instance.attachModel(model);
@@ -125,7 +128,7 @@ describe('Multi-file editor library', () => {
});
it('attaches the model to the dirty diff controller', () => {
- spyOn(instance.dirtyDiffController, 'attachModel');
+ jest.spyOn(instance.dirtyDiffController, 'attachModel').mockImplementation(() => {});
instance.attachModel(model);
@@ -133,7 +136,7 @@ describe('Multi-file editor library', () => {
});
it('re-decorates with the dirty diff controller', () => {
- spyOn(instance.dirtyDiffController, 'reDecorate');
+ jest.spyOn(instance.dirtyDiffController, 'reDecorate').mockImplementation(() => {});
instance.attachModel(model);
@@ -155,7 +158,7 @@ describe('Multi-file editor library', () => {
});
it('sets original & modified', () => {
- spyOn(instance.instance, 'setModel');
+ jest.spyOn(instance.instance, 'setModel').mockImplementation(() => {});
instance.attachMergeRequestModel(model);
@@ -170,7 +173,7 @@ describe('Multi-file editor library', () => {
it('resets the editor model', () => {
instance.createInstance(document.createElement('div'));
- spyOn(instance.instance, 'setModel');
+ jest.spyOn(instance.instance, 'setModel').mockImplementation(() => {});
instance.clearEditor();
@@ -180,7 +183,7 @@ describe('Multi-file editor library', () => {
describe('dispose', () => {
it('calls disposble dispose method', () => {
- spyOn(instance.disposable, 'dispose').and.callThrough();
+ jest.spyOn(instance.disposable, 'dispose');
instance.dispose();
@@ -198,7 +201,7 @@ describe('Multi-file editor library', () => {
});
it('does not dispose modelManager', () => {
- spyOn(instance.modelManager, 'dispose');
+ jest.spyOn(instance.modelManager, 'dispose').mockImplementation(() => {});
instance.dispose();
@@ -206,7 +209,7 @@ describe('Multi-file editor library', () => {
});
it('does not dispose decorationsController', () => {
- spyOn(instance.decorationsController, 'dispose');
+ jest.spyOn(instance.decorationsController, 'dispose').mockImplementation(() => {});
instance.dispose();
@@ -219,7 +222,7 @@ describe('Multi-file editor library', () => {
it('does not update options', () => {
instance.createInstance(holder);
- spyOn(instance.instance, 'updateOptions');
+ jest.spyOn(instance.instance, 'updateOptions').mockImplementation(() => {});
instance.updateDiffView();
@@ -231,11 +234,11 @@ describe('Multi-file editor library', () => {
beforeEach(() => {
instance.createDiffInstance(holder);
- spyOn(instance.instance, 'updateOptions').and.callThrough();
+ jest.spyOn(instance.instance, 'updateOptions');
});
it('sets renderSideBySide to false if el is less than 700 pixels', () => {
- spyOnProperty(instance.instance.getDomNode(), 'offsetWidth').and.returnValue(600);
+ setNodeOffsetWidth(600);
expect(instance.instance.updateOptions).not.toHaveBeenCalledWith({
renderSideBySide: false,
@@ -243,7 +246,7 @@ describe('Multi-file editor library', () => {
});
it('sets renderSideBySide to false if el is more than 700 pixels', () => {
- spyOnProperty(instance.instance.getDomNode(), 'offsetWidth').and.returnValue(800);
+ setNodeOffsetWidth(800);
expect(instance.instance.updateOptions).not.toHaveBeenCalledWith({
renderSideBySide: true,
@@ -269,7 +272,7 @@ describe('Multi-file editor library', () => {
it('sets quickSuggestions to false when language is markdown', () => {
instance.createInstance(holder);
- spyOn(instance.instance, 'updateOptions').and.callThrough();
+ jest.spyOn(instance.instance, 'updateOptions');
const model = instance.createModel({
...file(),
diff --git a/spec/frontend/mocks/ce/diffs/workers/tree_worker.js b/spec/frontend/mocks/ce/diffs/workers/tree_worker.js
index a33ddbbfe63..5532a22f8e6 100644
--- a/spec/frontend/mocks/ce/diffs/workers/tree_worker.js
+++ b/spec/frontend/mocks/ce/diffs/workers/tree_worker.js
@@ -1,8 +1 @@
-/* eslint-disable class-methods-use-this */
-export default class TreeWorkerMock {
- addEventListener() {}
-
- terminate() {}
-
- postMessage() {}
-}
+export { default } from 'helpers/web_worker_mock';
diff --git a/spec/frontend/mocks/ce/ide/lib/diff/diff_worker.js b/spec/frontend/mocks/ce/ide/lib/diff/diff_worker.js
new file mode 100644
index 00000000000..5532a22f8e6
--- /dev/null
+++ b/spec/frontend/mocks/ce/ide/lib/diff/diff_worker.js
@@ -0,0 +1 @@
+export { default } from 'helpers/web_worker_mock';
diff --git a/spec/javascripts/ide/components/nav_dropdown_spec.js b/spec/javascripts/ide/components/nav_dropdown_spec.js
deleted file mode 100644
index dfb4d03540f..00000000000
--- a/spec/javascripts/ide/components/nav_dropdown_spec.js
+++ /dev/null
@@ -1,80 +0,0 @@
-import $ from 'jquery';
-import Vue from 'vue';
-import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import store from '~/ide/stores';
-import NavDropdown from '~/ide/components/nav_dropdown.vue';
-import { PERMISSION_READ_MR } from '~/ide/constants';
-
-const TEST_PROJECT_ID = 'lorem-ipsum';
-
-describe('IDE NavDropdown', () => {
- const Component = Vue.extend(NavDropdown);
- let vm;
- let $dropdown;
-
- beforeEach(() => {
- store.state.currentProjectId = TEST_PROJECT_ID;
- Vue.set(store.state.projects, TEST_PROJECT_ID, {
- userPermissions: {
- [PERMISSION_READ_MR]: true,
- },
- });
- vm = mountComponentWithStore(Component, { store });
- $dropdown = $(vm.$el);
-
- // block dispatch from doing anything
- spyOn(vm.$store, 'dispatch');
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- const findIcon = name => vm.$el.querySelector(`.ic-${name}`);
- const findMRIcon = () => findIcon('merge-request');
-
- it('renders nothing initially', () => {
- expect(vm.$el).not.toContainElement('.ide-nav-form');
- });
-
- it('renders nav form when show.bs.dropdown', done => {
- $dropdown.trigger('show.bs.dropdown');
-
- vm.$nextTick()
- .then(() => {
- expect(vm.$el).toContainElement('.ide-nav-form');
- })
- .then(done)
- .catch(done.fail);
- });
-
- it('destroys nav form when closed', done => {
- $dropdown.trigger('show.bs.dropdown');
- $dropdown.trigger('hide.bs.dropdown');
-
- vm.$nextTick()
- .then(() => {
- expect(vm.$el).not.toContainElement('.ide-nav-form');
- })
- .then(done)
- .catch(done.fail);
- });
-
- it('renders merge request icon', () => {
- expect(findMRIcon()).not.toBeNull();
- });
-
- describe('when user cannot read merge requests', () => {
- beforeEach(done => {
- store.state.projects[TEST_PROJECT_ID].userPermissions = {};
-
- vm.$nextTick()
- .then(done)
- .catch(done.fail);
- });
-
- it('does not render merge requests', () => {
- expect(findMRIcon()).toBeNull();
- });
- });
-});
diff --git a/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb
index 08d3b7bec6a..14657c88b18 100644
--- a/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb
@@ -2,13 +2,30 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::BackfillSnippetRepositories, :migration, schema: 2020_02_26_162723 do
+describe Gitlab::BackgroundMigration::BackfillSnippetRepositories, :migration, schema: 2020_04_20_094444 do
let(:gitlab_shell) { Gitlab::Shell.new }
let(:users) { table(:users) }
let(:snippets) { table(:snippets) }
let(:snippet_repositories) { table(:snippet_repositories) }
- let(:user) { users.create(id: 1, email: 'user@example.com', projects_limit: 10, username: 'test', name: 'Test') }
+ let(:user_state) { 'active' }
+ let(:ghost) { false }
+ let(:user_type) { nil }
+
+ let!(:user) do
+ users.create(id: 1,
+ email: 'user@example.com',
+ projects_limit: 10,
+ username: 'test',
+ name: 'Test',
+ state: user_state,
+ ghost: ghost,
+ last_activity_on: 1.minute.ago,
+ user_type: user_type,
+ confirmed_at: 1.day.ago)
+ end
+
+ let!(:admin) { users.create(id: 2, email: 'admin@example.com', projects_limit: 10, username: 'admin', name: 'Admin', admin: true, state: 'active') }
let!(:snippet_with_repo) { snippets.create(id: 1, type: 'PersonalSnippet', author_id: user.id, file_name: file_name, content: content) }
let!(:snippet_with_empty_repo) { snippets.create(id: 2, type: 'PersonalSnippet', author_id: user.id, file_name: file_name, content: content) }
let!(:snippet_without_repo) { snippets.create(id: 3, type: 'PersonalSnippet', author_id: user.id, file_name: file_name, content: content) }
@@ -54,14 +71,51 @@ describe Gitlab::BackgroundMigration::BackfillSnippetRepositories, :migration, s
end
shared_examples 'commits the file to the repository' do
- it do
- subject
+ context 'when author can update snippet and use git' do
+ it 'creates the repository and commit the file' do
+ subject
+
+ blob = blob_at(snippet, file_name)
+ last_commit = raw_repository(snippet).commit
+
+ aggregate_failures do
+ expect(blob).to be
+ expect(blob.data).to eq content
+ expect(last_commit.author_name).to eq user.name
+ expect(last_commit.author_email).to eq user.email
+ end
+ end
+ end
- blob = blob_at(snippet, file_name)
+ context 'when author cannot update snippet or use git' do
+ shared_examples 'admin user commits files' do
+ it do
+ subject
- aggregate_failures do
- expect(blob).to be
- expect(blob.data).to eq content
+ last_commit = raw_repository(snippet).commit
+
+ expect(last_commit.author_name).to eq admin.name
+ expect(last_commit.author_email).to eq admin.email
+ end
+ end
+
+ context 'when user is blocked' do
+ let(:user_state) { 'blocked' }
+
+ it_behaves_like 'admin user commits files'
+ end
+
+ context 'when user is deactivated' do
+ let(:user_state) { 'deactivated' }
+
+ it_behaves_like 'admin user commits files'
+ end
+
+ context 'when user is a ghost' do
+ let(:ghost) { true }
+ let(:user_type) { 'ghost' }
+
+ it_behaves_like 'admin user commits files'
end
end
end
diff --git a/spec/migrations/backfill_snippet_repositories_spec.rb b/spec/migrations/backfill_snippet_repositories_spec.rb
new file mode 100644
index 00000000000..e87bf7376dd
--- /dev/null
+++ b/spec/migrations/backfill_snippet_repositories_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20200420094444_backfill_snippet_repositories.rb')
+
+describe BackfillSnippetRepositories do
+ let(:users) { table(:users) }
+ let(:snippets) { table(:snippets) }
+ let(:user) { users.create(id: 1, email: 'user@example.com', projects_limit: 10, username: 'test', name: 'Test', state: 'active') }
+
+ def create_snippet(id)
+ params = {
+ id: id,
+ type: 'PersonalSnippet',
+ author_id: user.id,
+ file_name: 'foo',
+ content: 'bar'
+ }
+
+ snippets.create!(params)
+ end
+
+ it 'correctly schedules background migrations' do
+ create_snippet(1)
+ create_snippet(2)
+ create_snippet(3)
+
+ stub_const("#{described_class.name}::BATCH_SIZE", 2)
+
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(described_class::MIGRATION)
+ .to be_scheduled_delayed_migration(3.minutes, 1, 2)
+
+ expect(described_class::MIGRATION)
+ .to be_scheduled_delayed_migration(6.minutes, 3, 3)
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq(2)
+ end
+ end
+ end
+end
diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb
index 15b162ae87a..356b0e18559 100644
--- a/spec/models/remote_mirror_spec.rb
+++ b/spec/models/remote_mirror_spec.rb
@@ -143,22 +143,54 @@ describe RemoteMirror, :mailer do
end
describe '#update_repository' do
- let(:git_remote_mirror) { spy }
+ it 'performs update including options' do
+ git_remote_mirror = stub_const('Gitlab::Git::RemoteMirror', spy)
+ mirror = build(:remote_mirror)
- before do
- stub_const('Gitlab::Git::RemoteMirror', git_remote_mirror)
+ expect(mirror).to receive(:options_for_update).and_return(options: true)
+ mirror.update_repository
+
+ expect(git_remote_mirror).to have_received(:new).with(
+ mirror.project.repository.raw,
+ mirror.remote_name,
+ options: true
+ )
+ expect(git_remote_mirror).to have_received(:update)
end
+ end
- it 'includes the `keep_divergent_refs` setting' do
+ describe '#options_for_update' do
+ it 'includes the `keep_divergent_refs` option' do
mirror = build_stubbed(:remote_mirror, keep_divergent_refs: true)
- mirror.update_repository({})
+ options = mirror.options_for_update
- expect(git_remote_mirror).to have_received(:new).with(
- anything,
- mirror.remote_name,
- hash_including(keep_divergent_refs: true)
- )
+ expect(options).to include(keep_divergent_refs: true)
+ end
+
+ it 'includes the `only_branches_matching` option' do
+ branch = create(:protected_branch)
+ mirror = build_stubbed(:remote_mirror, project: branch.project, only_protected_branches: true)
+
+ options = mirror.options_for_update
+
+ expect(options).to include(only_branches_matching: [branch.name])
+ end
+
+ it 'includes the `ssh_key` option' do
+ mirror = build(:remote_mirror, :ssh, ssh_private_key: 'private-key')
+
+ options = mirror.options_for_update
+
+ expect(options).to include(ssh_key: 'private-key')
+ end
+
+ it 'includes the `known_hosts` option' do
+ mirror = build(:remote_mirror, :ssh, ssh_known_hosts: 'known-hosts')
+
+ options = mirror.options_for_update
+
+ expect(options).to include(known_hosts: 'known-hosts')
end
end
diff --git a/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb b/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb
index 351d2db8973..cb9aeea74b2 100644
--- a/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb
@@ -6,9 +6,10 @@ describe 'Destroying a Snippet' do
include GraphqlHelpers
let(:current_user) { snippet.author }
+ let(:snippet_gid) { snippet.to_global_id.to_s }
let(:mutation) do
variables = {
- id: snippet.to_global_id.to_s
+ id: snippet_gid
}
graphql_mutation(:destroy_snippet, variables)
@@ -49,9 +50,11 @@ describe 'Destroying a Snippet' do
end
describe 'PersonalSnippet' do
- it_behaves_like 'graphql delete actions' do
- let_it_be(:snippet) { create(:personal_snippet) }
- end
+ let_it_be(:snippet) { create(:personal_snippet) }
+
+ it_behaves_like 'graphql delete actions'
+
+ it_behaves_like 'when the snippet is not found'
end
describe 'ProjectSnippet' do
@@ -85,5 +88,7 @@ describe 'Destroying a Snippet' do
end
end
end
+
+ it_behaves_like 'when the snippet is not found'
end
end
diff --git a/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb b/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb
index 05e3f7e6806..dbcf177ee09 100644
--- a/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb
@@ -10,9 +10,11 @@ describe 'Mark snippet as spam', :do_not_mock_admin_mode do
let_it_be(:snippet) { create(:personal_snippet) }
let_it_be(:user_agent_detail) { create(:user_agent_detail, subject: snippet) }
let(:current_user) { snippet.author }
+
+ let(:snippet_gid) { snippet.to_global_id.to_s }
let(:mutation) do
variables = {
- id: snippet.to_global_id.to_s
+ id: snippet_gid
}
graphql_mutation(:mark_as_spam_snippet, variables)
@@ -30,6 +32,8 @@ describe 'Mark snippet as spam', :do_not_mock_admin_mode do
end
end
+ it_behaves_like 'when the snippet is not found'
+
context 'when the user does not have permission' do
let(:current_user) { other_user }
diff --git a/spec/requests/api/graphql/mutations/snippets/update_spec.rb b/spec/requests/api/graphql/mutations/snippets/update_spec.rb
index 1035e3346e1..968ea5aed52 100644
--- a/spec/requests/api/graphql/mutations/snippets/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/update_spec.rb
@@ -15,9 +15,10 @@ describe 'Updating a Snippet' do
let(:updated_file_name) { 'Updated file_name' }
let(:current_user) { snippet.author }
+ let(:snippet_gid) { GitlabSchema.id_from_object(snippet).to_s }
let(:mutation) do
variables = {
- id: GitlabSchema.id_from_object(snippet).to_s,
+ id: snippet_gid,
content: updated_content,
description: updated_description,
visibility_level: 'public',
@@ -90,16 +91,18 @@ describe 'Updating a Snippet' do
end
describe 'PersonalSnippet' do
- it_behaves_like 'graphql update actions' do
- let(:snippet) do
- create(:personal_snippet,
- :private,
- file_name: original_file_name,
- title: original_title,
- content: original_content,
- description: original_description)
- end
+ let(:snippet) do
+ create(:personal_snippet,
+ :private,
+ file_name: original_file_name,
+ title: original_title,
+ content: original_content,
+ description: original_description)
end
+
+ it_behaves_like 'graphql update actions'
+
+ it_behaves_like 'when the snippet is not found'
end
describe 'ProjectSnippet' do
@@ -142,5 +145,7 @@ describe 'Updating a Snippet' do
end
end
end
+
+ it_behaves_like 'when the snippet is not found'
end
end
diff --git a/spec/services/projects/update_remote_mirror_service_spec.rb b/spec/services/projects/update_remote_mirror_service_spec.rb
index 4396ccab584..38c2dc0780e 100644
--- a/spec/services/projects/update_remote_mirror_service_spec.rb
+++ b/spec/services/projects/update_remote_mirror_service_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
describe Projects::UpdateRemoteMirrorService do
let(:project) { create(:project, :repository) }
let(:remote_project) { create(:forked_project_with_submodules) }
- let(:remote_mirror) { project.remote_mirrors.create!(url: remote_project.http_url_to_repo, enabled: true, only_protected_branches: false) }
+ let(:remote_mirror) { create(:remote_mirror, project: project, enabled: true) }
let(:remote_name) { remote_mirror.remote_name }
subject(:service) { described_class.new(project, project.creator) }
@@ -16,7 +16,9 @@ describe Projects::UpdateRemoteMirrorService do
before do
project.repository.add_branch(project.owner, 'existing-branch', 'master')
- allow(remote_mirror).to receive(:update_repository).and_return(true)
+ allow(remote_mirror)
+ .to receive(:update_repository)
+ .and_return(double(divergent_refs: []))
end
it 'ensures the remote exists' do
@@ -53,7 +55,7 @@ describe Projects::UpdateRemoteMirrorService do
it 'marks the mirror as failed and raises the error when an unexpected error occurs' do
allow(project.repository).to receive(:fetch_remote).and_raise('Badly broken')
- expect { execute! }.to raise_error /Badly broken/
+ expect { execute! }.to raise_error(/Badly broken/)
expect(remote_mirror).to be_failed
expect(remote_mirror.last_error).to include('Badly broken')
@@ -83,32 +85,21 @@ describe Projects::UpdateRemoteMirrorService do
end
end
- context 'when syncing all branches' do
- it 'push all the branches the first time' do
+ context 'when there are divergent refs' do
+ before do
stub_fetch_remote(project, remote_name: remote_name, ssh_auth: remote_mirror)
-
- expect(remote_mirror).to receive(:update_repository).with({})
-
- execute!
end
- end
- context 'when only syncing protected branches' do
- it 'sync updated protected branches' do
- stub_fetch_remote(project, remote_name: remote_name, ssh_auth: remote_mirror)
- protected_branch = create_protected_branch(project)
- remote_mirror.only_protected_branches = true
-
- expect(remote_mirror)
- .to receive(:update_repository)
- .with(only_branches_matching: [protected_branch.name])
+ it 'marks the mirror as failed and sets an error message' do
+ response = double(divergent_refs: %w[refs/heads/master refs/heads/develop])
+ expect(remote_mirror).to receive(:update_repository).and_return(response)
execute!
- end
- def create_protected_branch(project)
- branch_name = project.repository.branch_names.find { |n| n != 'existing-branch' }
- create(:protected_branch, project: project, name: branch_name)
+ expect(remote_mirror).to be_failed
+ expect(remote_mirror.last_error).to include("Some refs have diverged")
+ expect(remote_mirror.last_error).to include("refs/heads/master\n")
+ expect(remote_mirror.last_error).to include("refs/heads/develop")
end
end
end
diff --git a/spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb
new file mode 100644
index 00000000000..48824a4b0d2
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'when the snippet is not found' do
+ let(:snippet_gid) do
+ "gid://gitlab/#{snippet.class.name}/#{non_existing_record_id}"
+ end
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
+end