diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-04-29 00:09:35 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-04-29 00:09:35 +0300 |
commit | abe11a6a2c04112d0b7d6d4facfd0c8370f51831 (patch) | |
tree | b0c9e9e019417e7b438bf24c6a4a28acfc0fd95b /spec | |
parent | 95e18e32833de71b46d73ead66c8f13e261af3f4 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r-- | spec/factories/remote_mirrors.rb | 5 | ||||
-rw-r--r-- | spec/frontend/helpers/web_worker_mock.js | 10 | ||||
-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.js | 102 | ||||
-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.js | 9 | ||||
-rw-r--r-- | spec/frontend/mocks/ce/ide/lib/diff/diff_worker.js | 1 | ||||
-rw-r--r-- | spec/javascripts/ide/components/nav_dropdown_spec.js | 80 | ||||
-rw-r--r-- | spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb | 70 | ||||
-rw-r--r-- | spec/migrations/backfill_snippet_repositories_spec.rb | 44 | ||||
-rw-r--r-- | spec/models/remote_mirror_spec.rb | 52 | ||||
-rw-r--r-- | spec/requests/api/graphql/mutations/snippets/destroy_spec.rb | 13 | ||||
-rw-r--r-- | spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb | 6 | ||||
-rw-r--r-- | spec/requests/api/graphql/mutations/snippets/update_spec.rb | 25 | ||||
-rw-r--r-- | spec/services/projects/update_remote_mirror_service_spec.rb | 37 | ||||
-rw-r--r-- | spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb | 10 |
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 |