diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-12-22 03:09:39 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-12-22 03:09:39 +0000 |
commit | f8edcff7e9aff93f8ac605c19e542204b0ed9ba2 (patch) | |
tree | fe45e8bc69f5c68c6d4ee7505a4d61c4fdb70299 /spec | |
parent | d61d19da54b0fb8fd54df4007fa95cd39db17e57 (diff) | |
download | gitlab-ce-f8edcff7e9aff93f8ac605c19e542204b0ed9ba2.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
42 files changed, 398 insertions, 1294 deletions
diff --git a/spec/controllers/admin/clusters_controller_spec.rb b/spec/controllers/admin/clusters_controller_spec.rb index c432adb6ae3..86a4ac61194 100644 --- a/spec/controllers/admin/clusters_controller_spec.rb +++ b/spec/controllers/admin/clusters_controller_spec.rb @@ -159,8 +159,6 @@ RSpec.describe Admin::ClustersController do describe 'functionality' do context 'when creates a cluster' do it 'creates a new cluster' do - expect(ClusterProvisionWorker).to receive(:perform_async) - expect { post_create_user }.to change { Clusters::Cluster.count } .and change { Clusters::Platforms::Kubernetes.count } @@ -187,8 +185,6 @@ RSpec.describe Admin::ClustersController do end it 'creates a new cluster' do - expect(ClusterProvisionWorker).to receive(:perform_async) - expect { post_create_user }.to change { Clusters::Cluster.count } .and change { Clusters::Platforms::Kubernetes.count } diff --git a/spec/controllers/concerns/check_rate_limit_spec.rb b/spec/controllers/concerns/check_rate_limit_spec.rb index 75776acd520..25574aa295b 100644 --- a/spec/controllers/concerns/check_rate_limit_spec.rb +++ b/spec/controllers/concerns/check_rate_limit_spec.rb @@ -33,8 +33,8 @@ RSpec.describe CheckRateLimit do end describe '#check_rate_limit!' do - it 'calls ApplicationRateLimiter#throttled? with the right arguments' do - expect(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).with(key, scope: scope).and_return(false) + it 'calls ApplicationRateLimiter#throttled_request? with the right arguments' do + expect(::Gitlab::ApplicationRateLimiter).to receive(:throttled_request?).with(request, user, key, scope: scope).and_return(false) expect(subject).not_to receive(:render) subject.check_rate_limit!(key, scope: scope) diff --git a/spec/controllers/groups/clusters_controller_spec.rb b/spec/controllers/groups/clusters_controller_spec.rb index eb3fe4bc330..46f507c34ba 100644 --- a/spec/controllers/groups/clusters_controller_spec.rb +++ b/spec/controllers/groups/clusters_controller_spec.rb @@ -180,8 +180,6 @@ RSpec.describe Groups::ClustersController do describe 'functionality' do context 'when creates a cluster' do it 'creates a new cluster' do - expect(ClusterProvisionWorker).to receive(:perform_async) - expect { go }.to change { Clusters::Cluster.count } .and change { Clusters::Platforms::Kubernetes.count } @@ -210,8 +208,6 @@ RSpec.describe Groups::ClustersController do end it 'creates a new cluster' do - expect(ClusterProvisionWorker).to receive(:perform_async) - expect { go }.to change { Clusters::Cluster.count } .and change { Clusters::Platforms::Kubernetes.count } diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb index 12202518e1e..894f0f8354d 100644 --- a/spec/controllers/projects/clusters_controller_spec.rb +++ b/spec/controllers/projects/clusters_controller_spec.rb @@ -181,8 +181,6 @@ RSpec.describe Projects::ClustersController do describe 'functionality' do context 'when creates a cluster' do it 'creates a new cluster' do - expect(ClusterProvisionWorker).to receive(:perform_async) - expect { go }.to change { Clusters::Cluster.count } .and change { Clusters::Platforms::Kubernetes.count } @@ -210,8 +208,6 @@ RSpec.describe Projects::ClustersController do end it 'creates a new cluster' do - expect(ClusterProvisionWorker).to receive(:perform_async) - expect { go }.to change { Clusters::Cluster.count } .and change { Clusters::Platforms::Kubernetes.count } diff --git a/spec/frontend/autosave_spec.js b/spec/frontend/autosave_spec.js index 7a9262cd004..88460221168 100644 --- a/spec/frontend/autosave_spec.js +++ b/spec/frontend/autosave_spec.js @@ -1,4 +1,3 @@ -import $ from 'jquery'; import { useLocalStorageSpy } from 'helpers/local_storage_helper'; import Autosave from '~/autosave'; import AccessorUtilities from '~/lib/utils/accessor'; @@ -7,12 +6,19 @@ describe('Autosave', () => { useLocalStorageSpy(); let autosave; - const field = $('<textarea></textarea>'); - const checkbox = $('<input type="checkbox">'); + const field = document.createElement('textarea'); + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; const key = 'key'; const fallbackKey = 'fallbackKey'; const lockVersionKey = 'lockVersionKey'; const lockVersion = 1; + const getAutosaveKey = () => `autosave/${key}`; + const getAutosaveLockKey = () => `autosave/${key}/lockVersion`; + + afterEach(() => { + autosave?.dispose?.(); + }); describe('class constructor', () => { beforeEach(() => { @@ -43,18 +49,10 @@ describe('Autosave', () => { }); describe('restore', () => { - beforeEach(() => { - autosave = { - field, - key, - }; - }); - describe('if .isLocalStorageAvailable is `false`', () => { beforeEach(() => { - autosave.isLocalStorageAvailable = false; - - Autosave.prototype.restore.call(autosave); + jest.spyOn(AccessorUtilities, 'canUseLocalStorage').mockReturnValue(false); + autosave = new Autosave(field, key); }); it('should not call .getItem', () => { @@ -63,97 +61,73 @@ describe('Autosave', () => { }); describe('if .isLocalStorageAvailable is `true`', () => { - beforeEach(() => { - autosave.isLocalStorageAvailable = true; - }); - it('should call .getItem', () => { - Autosave.prototype.restore.call(autosave); - - expect(window.localStorage.getItem).toHaveBeenCalledWith(key); + autosave = new Autosave(field, key); + expect(window.localStorage.getItem.mock.calls).toEqual([[getAutosaveKey()], []]); }); - it('triggers jquery event', () => { - jest.spyOn(autosave.field, 'trigger').mockImplementation(() => {}); - - Autosave.prototype.restore.call(autosave); - - expect(field.trigger).toHaveBeenCalled(); - }); - - it('triggers native event', () => { - const fieldElement = autosave.field.get(0); - const eventHandler = jest.fn(); - fieldElement.addEventListener('change', eventHandler); - - Autosave.prototype.restore.call(autosave); + describe('if saved value is present', () => { + const storedValue = 'bar'; - expect(eventHandler).toHaveBeenCalledTimes(1); - fieldElement.removeEventListener('change', eventHandler); - }); - - describe('if field type is checkbox', () => { beforeEach(() => { - autosave = { - field: checkbox, - key, - isLocalStorageAvailable: true, - type: 'checkbox', - }; + field.value = 'foo'; + window.localStorage.setItem(getAutosaveKey(), storedValue); }); - it('should restore', () => { - window.localStorage.setItem(key, true); - expect(checkbox.is(':checked')).toBe(false); - Autosave.prototype.restore.call(autosave); - expect(checkbox.is(':checked')).toBe(true); + it('restores the value', () => { + autosave = new Autosave(field, key); + expect(field.value).toEqual(storedValue); }); - }); - }); - describe('if field gets deleted from DOM', () => { - beforeEach(() => { - autosave.field = $('.not-a-real-element'); - }); + it('triggers native event', () => { + const eventHandler = jest.fn(); + field.addEventListener('change', eventHandler); + autosave = new Autosave(field, key); - it('does not trigger event', () => { - jest.spyOn(field, 'trigger'); + expect(eventHandler).toHaveBeenCalledTimes(1); + field.removeEventListener('change', eventHandler); + }); + + describe('if field type is checkbox', () => { + beforeEach(() => { + checkbox.checked = false; + window.localStorage.setItem(getAutosaveKey(), true); + autosave = new Autosave(checkbox, key); + }); - expect(field.trigger).not.toHaveBeenCalled(); + it('should restore', () => { + expect(checkbox.checked).toBe(true); + }); + }); }); }); }); describe('getSavedLockVersion', () => { - beforeEach(() => { - autosave = { - field, - key, - lockVersionKey, - }; - }); - describe('if .isLocalStorageAvailable is `false`', () => { beforeEach(() => { - autosave.isLocalStorageAvailable = false; - - Autosave.prototype.getSavedLockVersion.call(autosave); + jest.spyOn(AccessorUtilities, 'canUseLocalStorage').mockReturnValue(false); + autosave = new Autosave(field, key); }); it('should not call .getItem', () => { + autosave.getSavedLockVersion(); expect(window.localStorage.getItem).not.toHaveBeenCalled(); }); }); describe('if .isLocalStorageAvailable is `true`', () => { beforeEach(() => { - autosave.isLocalStorageAvailable = true; + autosave = new Autosave(field, key); }); it('should call .getItem', () => { - Autosave.prototype.getSavedLockVersion.call(autosave); - - expect(window.localStorage.getItem).toHaveBeenCalledWith(lockVersionKey); + autosave.getSavedLockVersion(); + expect(window.localStorage.getItem.mock.calls).toEqual([ + [getAutosaveKey()], + [], + [getAutosaveLockKey()], + ]); }); }); }); @@ -162,7 +136,7 @@ describe('Autosave', () => { beforeEach(() => { autosave = { reset: jest.fn() }; autosave.field = field; - field.val('value'); + field.value = 'value'; }); describe('if .isLocalStorageAvailable is `false`', () => { @@ -200,14 +174,14 @@ describe('Autosave', () => { }); it('should save true when checkbox on', () => { - checkbox.prop('checked', true); + checkbox.checked = true; Autosave.prototype.save.call(autosave); expect(window.localStorage.setItem).toHaveBeenCalledWith(key, true); }); it('should call reset when checkbox off', () => { autosave.reset = jest.fn(); - checkbox.prop('checked', false); + checkbox.checked = false; Autosave.prototype.save.call(autosave); expect(autosave.reset).toHaveBeenCalled(); expect(window.localStorage.setItem).not.toHaveBeenCalled(); diff --git a/spec/frontend/batch_comments/components/submit_dropdown_spec.js b/spec/frontend/batch_comments/components/submit_dropdown_spec.js index 462ef7e7280..003a6d86371 100644 --- a/spec/frontend/batch_comments/components/submit_dropdown_spec.js +++ b/spec/frontend/batch_comments/components/submit_dropdown_spec.js @@ -3,6 +3,8 @@ import Vuex from 'vuex'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import SubmitDropdown from '~/batch_comments/components/submit_dropdown.vue'; +jest.mock('~/autosave'); + Vue.use(Vuex); let wrapper; diff --git a/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js b/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js index 5fd61b25edc..f4d4f9cf896 100644 --- a/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js +++ b/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js @@ -5,6 +5,7 @@ import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_m import DesignReplyForm from '~/design_management/components/design_notes/design_reply_form.vue'; jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'); +jest.mock('~/autosave'); describe('Design reply form component', () => { let wrapper; @@ -78,12 +79,11 @@ describe('Design reply form component', () => { createComponent({ discussionId }); await nextTick(); - // We discourage testing `wrapper.vm` properties but - // since `autosave` library instantiates on component - // there's no other way to test whether instantiation - // happened correctly or not. - expect(wrapper.vm.autosaveDiscussion).toBeInstanceOf(Autosave); - expect(wrapper.vm.autosaveDiscussion.key).toBe(`autosave/Discussion/6/${shortDiscussionId}`); + expect(Autosave).toHaveBeenCalledWith(expect.any(Element), [ + 'Discussion', + 6, + shortDiscussionId, + ]); }, ); @@ -141,7 +141,7 @@ describe('Design reply form component', () => { }); it('emits submitForm event on Comment button click', async () => { - const autosaveResetSpy = jest.spyOn(wrapper.vm.autosaveDiscussion, 'reset'); + const autosaveResetSpy = jest.spyOn(Autosave.prototype, 'reset'); findSubmitButton().vm.$emit('click'); @@ -151,7 +151,7 @@ describe('Design reply form component', () => { }); it('emits submitForm event on textarea ctrl+enter keydown', async () => { - const autosaveResetSpy = jest.spyOn(wrapper.vm.autosaveDiscussion, 'reset'); + const autosaveResetSpy = jest.spyOn(Autosave.prototype, 'reset'); findTextarea().trigger('keydown.enter', { ctrlKey: true, @@ -163,7 +163,7 @@ describe('Design reply form component', () => { }); it('emits submitForm event on textarea meta+enter keydown', async () => { - const autosaveResetSpy = jest.spyOn(wrapper.vm.autosaveDiscussion, 'reset'); + const autosaveResetSpy = jest.spyOn(Autosave.prototype, 'reset'); findTextarea().trigger('keydown.enter', { metaKey: true, @@ -178,7 +178,7 @@ describe('Design reply form component', () => { findTextarea().setValue('test2'); await nextTick(); - expect(wrapper.emitted('input')).toEqual([['test'], ['test2']]); + expect(wrapper.emitted('input')).toEqual([['test2']]); }); it('emits cancelForm event on Escape key if text was not changed', () => { @@ -211,7 +211,7 @@ describe('Design reply form component', () => { it('emits cancelForm event when confirmed', async () => { confirmAction.mockResolvedValueOnce(true); - const autosaveResetSpy = jest.spyOn(wrapper.vm.autosaveDiscussion, 'reset'); + const autosaveResetSpy = jest.spyOn(Autosave.prototype, 'reset'); wrapper.setProps({ value: 'test3' }); await nextTick(); @@ -228,7 +228,7 @@ describe('Design reply form component', () => { it("doesn't emit cancelForm event when not confirmed", async () => { confirmAction.mockResolvedValueOnce(false); - const autosaveResetSpy = jest.spyOn(wrapper.vm.autosaveDiscussion, 'reset'); + const autosaveResetSpy = jest.spyOn(Autosave.prototype, 'reset'); wrapper.setProps({ value: 'test3' }); await nextTick(); diff --git a/spec/frontend/diffs/components/diff_line_note_form_spec.js b/spec/frontend/diffs/components/diff_line_note_form_spec.js index 9493dc8855e..bd0e3455872 100644 --- a/spec/frontend/diffs/components/diff_line_note_form_spec.js +++ b/spec/frontend/diffs/components/diff_line_note_form_spec.js @@ -101,7 +101,8 @@ describe('DiffLineNoteForm', () => { }); it('should init autosave', () => { - expect(Autosave).toHaveBeenCalledWith({}, [ + // we're using shallow mount here so there's no element to pass to Autosave + expect(Autosave).toHaveBeenCalledWith(undefined, [ 'Note', 'Issue', 98, diff --git a/spec/frontend/issuable/issuable_form_spec.js b/spec/frontend/issuable/issuable_form_spec.js index 5e67ea42b87..28ec0e22d8b 100644 --- a/spec/frontend/issuable/issuable_form_spec.js +++ b/spec/frontend/issuable/issuable_form_spec.js @@ -35,8 +35,8 @@ describe('IssuableForm', () => { let $description; beforeEach(() => { - $title = $form.find('input[name*="[title]"]'); - $description = $form.find('textarea[name*="[description]"]'); + $title = $form.find('input[name*="[title]"]').get(0); + $description = $form.find('textarea[name*="[description]"]').get(0); }); afterEach(() => { @@ -103,7 +103,11 @@ describe('IssuableForm', () => { createIssuable($form); expect(Autosave).toHaveBeenCalledTimes(totalAutosaveFormFields); - expect(Autosave).toHaveBeenLastCalledWith($input, ['/', '', id], `autosave///=${id}`); + expect(Autosave).toHaveBeenLastCalledWith( + $input.get(0), + ['/', '', id], + `autosave///=${id}`, + ); }); }); diff --git a/spec/frontend/notes/components/comment_form_spec.js b/spec/frontend/notes/components/comment_form_spec.js index 701ff492702..e13985ef469 100644 --- a/spec/frontend/notes/components/comment_form_spec.js +++ b/spec/frontend/notes/components/comment_form_spec.js @@ -5,6 +5,7 @@ import MockAdapter from 'axios-mock-adapter'; import Vue, { nextTick } from 'vue'; import Vuex from 'vuex'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import Autosave from '~/autosave'; import batchComments from '~/batch_comments/stores/modules/batch_comments'; import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests'; import { createAlert } from '~/flash'; @@ -20,6 +21,7 @@ import { loggedOutnoteableData, notesDataMock, userDataMock, noteableDataMock } jest.mock('autosize'); jest.mock('~/commons/nav/user_merge_requests'); jest.mock('~/flash'); +jest.mock('~/autosave'); Vue.use(Vuex); @@ -336,8 +338,11 @@ describe('issue_comment_form component', () => { }); it('inits autosave', () => { - expect(wrapper.vm.autosave).toBeDefined(); - expect(wrapper.vm.autosave.key).toBe(`autosave/Note/Issue/${noteableDataMock.id}`); + expect(Autosave).toHaveBeenCalledWith(expect.any(Element), [ + 'Note', + 'Issue', + noteableDataMock.id, + ]); }); }); diff --git a/spec/frontend/notes/components/note_body_spec.js b/spec/frontend/notes/components/note_body_spec.js index 3b5313744ff..c71cf7666ab 100644 --- a/spec/frontend/notes/components/note_body_spec.js +++ b/spec/frontend/notes/components/note_body_spec.js @@ -7,11 +7,14 @@ import NoteAwardsList from '~/notes/components/note_awards_list.vue'; import NoteForm from '~/notes/components/note_form.vue'; import createStore from '~/notes/stores'; import notes from '~/notes/stores/modules/index'; +import Autosave from '~/autosave'; import Suggestions from '~/vue_shared/components/markdown/suggestions.vue'; import { noteableDataMock, notesDataMock, note } from '../mock_data'; +jest.mock('~/autosave'); + const createComponent = ({ props = {}, noteableData = noteableDataMock, @@ -84,13 +87,8 @@ describe('issue_note_body component', () => { }); it('adds autosave', () => { - const autosaveKey = `autosave/Note/${note.noteable_type}/${note.id}`; - - // While we discourage testing wrapper props - // here we aren't testing a component prop - // but instead an instance object property - // which is defined in `app/assets/javascripts/notes/mixins/autosave.js` - expect(wrapper.vm.autosave.key).toEqual(autosaveKey); + // passing undefined instead of an element because of shallowMount + expect(Autosave).toHaveBeenCalledWith(undefined, ['Note', note.noteable_type, note.id]); }); describe('isInternalNote', () => { diff --git a/spec/frontend/vue_shared/components/listbox_input/listbox_input_spec.js b/spec/frontend/vue_shared/components/listbox_input/listbox_input_spec.js index cb7262b15e3..2b62cbb9ab3 100644 --- a/spec/frontend/vue_shared/components/listbox_input/listbox_input_spec.js +++ b/spec/frontend/vue_shared/components/listbox_input/listbox_input_spec.js @@ -1,11 +1,12 @@ import { shallowMount } from '@vue/test-utils'; -import { GlListbox } from '@gitlab/ui'; +import { GlFormGroup, GlListbox } from '@gitlab/ui'; import ListboxInput from '~/vue_shared/components/listbox_input/listbox_input.vue'; describe('ListboxInput', () => { let wrapper; // Props + const label = 'label'; const name = 'name'; const defaultToggleText = 'defaultToggleText'; const items = [ @@ -23,12 +24,14 @@ describe('ListboxInput', () => { ]; // Finders + const findGlFormGroup = () => wrapper.findComponent(GlFormGroup); const findGlListbox = () => wrapper.findComponent(GlListbox); const findInput = () => wrapper.find('input'); const createComponent = (propsData) => { wrapper = shallowMount(ListboxInput, { propsData: { + label, name, defaultToggleText, items, @@ -37,14 +40,22 @@ describe('ListboxInput', () => { }); }; - describe('input attributes', () => { + describe('options', () => { beforeEach(() => { createComponent(); }); + it('passes the label to the form group', () => { + expect(findGlFormGroup().attributes('label')).toBe(label); + }); + it('sets the input name', () => { expect(findInput().attributes('name')).toBe(name); }); + + it('is not filterable with few items', () => { + expect(findGlListbox().props('searchable')).toBe(false); + }); }); describe('toggle text', () => { @@ -91,12 +102,29 @@ describe('ListboxInput', () => { }); describe('search', () => { - beforeEach(() => { - createComponent(); + it('is searchable when there are more than 10 items', () => { + createComponent({ + items: [ + { + text: 'Group 1', + options: [...Array(10).keys()].map((index) => ({ + text: index + 1, + value: String(index + 1), + })), + }, + { + text: 'Group 2', + options: [{ text: 'Item 11', value: '11' }], + }, + ], + }); + + expect(findGlListbox().props('searchable')).toBe(true); }); it('passes all items to GlListbox by default', () => { createComponent(); + expect(findGlListbox().props('items')).toStrictEqual(items); }); diff --git a/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js b/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js index 33f370efdfa..5461d38599d 100644 --- a/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js +++ b/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js @@ -90,6 +90,17 @@ describe('Source Viewer component', () => { }); }); + describe('legacy fallbacks', () => { + it('tracks a fallback event and emits an error when viewing python files', () => { + const fallbackLanguage = 'python'; + const eventData = { label: EVENT_LABEL_FALLBACK, property: fallbackLanguage }; + createComponent({ language: fallbackLanguage }); + + expect(Tracking.event).toHaveBeenCalledWith(undefined, EVENT_ACTION, eventData); + expect(wrapper.emitted('error')).toHaveLength(1); + }); + }); + describe('highlight.js', () => { beforeEach(() => createComponent({ language: mappedLanguage })); @@ -114,10 +125,10 @@ describe('Source Viewer component', () => { }); it('correctly maps languages starting with uppercase', async () => { - await createComponent({ language: 'Python3' }); - const languageDefinition = await import(`highlight.js/lib/languages/python`); + await createComponent({ language: 'Ruby' }); + const languageDefinition = await import(`highlight.js/lib/languages/ruby`); - expect(hljs.registerLanguage).toHaveBeenCalledWith('python', languageDefinition.default); + expect(hljs.registerLanguage).toHaveBeenCalledWith('ruby', languageDefinition.default); }); it('highlights the first chunk', () => { diff --git a/spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js b/spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js index e5594b6d37e..159be4cd1ef 100644 --- a/spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js +++ b/spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js @@ -5,9 +5,12 @@ import { nextTick } from 'vue'; import IssuableEditForm from '~/vue_shared/issuable/show/components/issuable_edit_form.vue'; import IssuableEventHub from '~/vue_shared/issuable/show/event_hub'; import MarkdownField from '~/vue_shared/components/markdown/field.vue'; +import Autosave from '~/autosave'; import { mockIssuableShowProps, mockIssuable } from '../mock_data'; +jest.mock('~/autosave'); + const issuableEditFormProps = { issuable: mockIssuable, ...mockIssuableShowProps, @@ -36,10 +39,12 @@ describe('IssuableEditForm', () => { beforeEach(() => { wrapper = createComponent(); + jest.spyOn(Autosave.prototype, 'reset'); }); afterEach(() => { wrapper.destroy(); + jest.resetAllMocks(); }); describe('watch', () => { @@ -100,21 +105,18 @@ describe('IssuableEditForm', () => { describe('methods', () => { describe('initAutosave', () => { - it('initializes `autosaveTitle` and `autosaveDescription` props', () => { - expect(wrapper.vm.autosaveTitle).toBeDefined(); - expect(wrapper.vm.autosaveDescription).toBeDefined(); + it('initializes autosave', () => { + expect(Autosave.mock.calls).toEqual([ + [expect.any(Element), ['/', '', 'title']], + [expect.any(Element), ['/', '', 'description']], + ]); }); }); describe('resetAutosave', () => { - it('calls `reset` on `autosaveTitle` and `autosaveDescription` props', () => { - jest.spyOn(wrapper.vm.autosaveTitle, 'reset').mockImplementation(jest.fn); - jest.spyOn(wrapper.vm.autosaveDescription, 'reset').mockImplementation(jest.fn); - - wrapper.vm.resetAutosave(); - - expect(wrapper.vm.autosaveTitle.reset).toHaveBeenCalled(); - expect(wrapper.vm.autosaveDescription.reset).toHaveBeenCalled(); + it('resets title and description on "update.issuable event"', () => { + IssuableEventHub.$emit('update.issuable'); + expect(Autosave.prototype.reset.mock.calls).toEqual([[], []]); }); }); }); diff --git a/spec/lib/api/helpers/rate_limiter_spec.rb b/spec/lib/api/helpers/rate_limiter_spec.rb index 3640c7e30e7..531140a32a3 100644 --- a/spec/lib/api/helpers/rate_limiter_spec.rb +++ b/spec/lib/api/helpers/rate_limiter_spec.rb @@ -31,8 +31,8 @@ RSpec.describe API::Helpers::RateLimiter do end describe '#check_rate_limit!' do - it 'calls ApplicationRateLimiter#throttled? with the right arguments' do - expect(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).with(key, scope: scope).and_return(false) + it 'calls ApplicationRateLimiter#throttled_request? with the right arguments' do + expect(::Gitlab::ApplicationRateLimiter).to receive(:throttled_request?).with(request, user, key, scope: scope).and_return(false) expect(subject).not_to receive(:render_api_error!) subject.check_rate_limit!(key, scope: scope) diff --git a/spec/lib/gitlab/application_rate_limiter_spec.rb b/spec/lib/gitlab/application_rate_limiter_spec.rb index 41e79f811fa..c938393adce 100644 --- a/spec/lib/gitlab/application_rate_limiter_spec.rb +++ b/spec/lib/gitlab/application_rate_limiter_spec.rb @@ -214,6 +214,52 @@ RSpec.describe Gitlab::ApplicationRateLimiter, :clean_gitlab_redis_rate_limiting end end + describe '.throttled_request?', :freeze_time do + let(:request) { instance_double('Rack::Request') } + + context 'when request is not over the limit' do + it 'returns false and does not log the request' do + expect(subject).not_to receive(:log_request) + + expect(subject.throttled_request?(request, user, :test_action, scope: [user])).to eq(false) + end + end + + context 'when request is over the limit' do + before do + subject.throttled?(:test_action, scope: [user]) + end + + it 'returns true and logs the request' do + expect(subject).to receive(:log_request).with(request, :test_action_request_limit, user) + + expect(subject.throttled_request?(request, user, :test_action, scope: [user])).to eq(true) + end + + context 'when the bypass header is set' do + before do + allow(Gitlab::Throttle).to receive(:bypass_header).and_return('SOME_HEADER') + end + + it 'skips rate limit if set to "1"' do + allow(request).to receive(:get_header).with(Gitlab::Throttle.bypass_header).and_return('1') + + expect(subject).not_to receive(:log_request) + + expect(subject.throttled_request?(request, user, :test_action, scope: [user])).to eq(false) + end + + it 'does not skip rate limit if set to something else than "1"' do + allow(request).to receive(:get_header).with(Gitlab::Throttle.bypass_header).and_return('0') + + expect(subject).to receive(:log_request).with(request, :test_action_request_limit, user) + + expect(subject.throttled_request?(request, user, :test_action, scope: [user])).to eq(true) + end + end + end + end + describe '.peek' do it 'peeks at the current state without changing its value' do freeze_time do diff --git a/spec/models/clusters/providers/aws_spec.rb b/spec/models/clusters/providers/aws_spec.rb index 2afed663edf..cb2960e1557 100644 --- a/spec/models/clusters/providers/aws_spec.rb +++ b/spec/models/clusters/providers/aws_spec.rb @@ -75,39 +75,6 @@ RSpec.describe Clusters::Providers::Aws do end end - describe '#api_client' do - let(:provider) { create(:cluster_provider_aws) } - let(:credentials) { double } - let(:client) { double } - - subject { provider.api_client } - - before do - allow(provider).to receive(:credentials).and_return(credentials) - - expect(Aws::CloudFormation::Client).to receive(:new) - .with(credentials: credentials, region: provider.region) - .and_return(client) - end - - it { is_expected.to eq client } - end - - describe '#credentials' do - let(:provider) { create(:cluster_provider_aws) } - let(:credentials) { double } - - subject { provider.credentials } - - before do - expect(Aws::Credentials).to receive(:new) - .with(provider.access_key_id, provider.secret_access_key, provider.session_token) - .and_return(credentials) - end - - it { is_expected.to eq credentials } - end - describe '#created_by_user' do let(:provider) { create(:cluster_provider_aws) } diff --git a/spec/models/clusters/providers/gcp_spec.rb b/spec/models/clusters/providers/gcp_spec.rb index a1f00069937..afd5699091a 100644 --- a/spec/models/clusters/providers/gcp_spec.rb +++ b/spec/models/clusters/providers/gcp_spec.rb @@ -111,31 +111,6 @@ RSpec.describe Clusters::Providers::Gcp do end end - describe '#api_client' do - subject { gcp.api_client } - - context 'when status is creating' do - let(:gcp) { build(:cluster_provider_gcp, :creating) } - - it 'returns Cloud Platform API clinet' do - expect(subject).to be_an_instance_of(GoogleApi::CloudPlatform::Client) - expect(subject.access_token).to eq(gcp.access_token) - end - end - - context 'when status is created' do - let(:gcp) { build(:cluster_provider_gcp, :created) } - - it { is_expected.to be_nil } - end - - context 'when status is errored' do - let(:gcp) { build(:cluster_provider_gcp, :errored) } - - it { is_expected.to be_nil } - end - end - describe '#nullify_credentials' do let(:provider) { create(:cluster_provider_gcp, :creating) } diff --git a/spec/requests/api/graphql/ci/jobs_spec.rb b/spec/requests/api/graphql/ci/jobs_spec.rb index 7a1dc614dcf..131cdb77107 100644 --- a/spec/requests/api/graphql/ci/jobs_spec.rb +++ b/spec/requests/api/graphql/ci/jobs_spec.rb @@ -88,10 +88,10 @@ RSpec.describe 'Query.project.pipeline', feature_category: :continuous_integrati build_stage = create(:ci_stage, position: 2, name: 'build', project: project, pipeline: pipeline) test_stage = create(:ci_stage, position: 3, name: 'test', project: project, pipeline: pipeline) - create(:ci_build, pipeline: pipeline, name: 'docker 1 2', scheduling_type: :stage, stage: build_stage, stage_idx: build_stage.position) - create(:ci_build, pipeline: pipeline, name: 'docker 2 2', stage: build_stage, stage_idx: build_stage.position, scheduling_type: :dag) - create(:ci_build, pipeline: pipeline, name: 'rspec 1 2', scheduling_type: :stage, stage: test_stage, stage_idx: test_stage.position) - test_job = create(:ci_build, pipeline: pipeline, name: 'rspec 2 2', scheduling_type: :dag, stage: test_stage, stage_idx: test_stage.position) + create(:ci_build, pipeline: pipeline, name: 'docker 1 2', scheduling_type: :stage, ci_stage: build_stage, stage_idx: build_stage.position) + create(:ci_build, pipeline: pipeline, name: 'docker 2 2', ci_stage: build_stage, stage_idx: build_stage.position, scheduling_type: :dag) + create(:ci_build, pipeline: pipeline, name: 'rspec 1 2', scheduling_type: :stage, ci_stage: test_stage, stage_idx: test_stage.position) + test_job = create(:ci_build, pipeline: pipeline, name: 'rspec 2 2', scheduling_type: :dag, ci_stage: test_stage, stage_idx: test_stage.position) create(:ci_build_need, build: test_job, name: 'my test job') end diff --git a/spec/requests/api/graphql/issues_spec.rb b/spec/requests/api/graphql/issues_spec.rb index ba6f8ec2cab..145e57bc6ae 100644 --- a/spec/requests/api/graphql/issues_spec.rb +++ b/spec/requests/api/graphql/issues_spec.rb @@ -177,6 +177,32 @@ RSpec.describe 'getting an issue list at root level', feature_category: :team_pl end end + context 'with rate limiting' do + it_behaves_like 'rate limited endpoint', rate_limit_key: :search_rate_limit, graphql: true do + let_it_be(:current_user) { developer } + + let(:error_message) do + 'This endpoint has been requested with the search argument too many times. Try again later.' + end + + def request + post_graphql(query({ search: 'test' }), current_user: developer) + end + end + + it_behaves_like 'rate limited endpoint', rate_limit_key: :search_rate_limit_unauthenticated, graphql: true do + let_it_be(:current_user) { nil } + + let(:error_message) do + 'This endpoint has been requested with the search argument too many times. Try again later.' + end + + def request + post_graphql(query({ search: 'test' })) + end + end + end + def execute_query post_query end diff --git a/spec/requests/api/issues/get_project_issues_spec.rb b/spec/requests/api/issues/get_project_issues_spec.rb index 70966d23576..6fc3903103b 100644 --- a/spec/requests/api/issues/get_project_issues_spec.rb +++ b/spec/requests/api/issues/get_project_issues_spec.rb @@ -11,15 +11,24 @@ RSpec.describe API::Issues, feature_category: :team_planning do let_it_be(:group) { create(:group, :public) } - let(:user2) { create(:user) } - let(:non_member) { create(:user) } + let_it_be(:user2) { create(:user) } + let_it_be(:non_member) { create(:user) } let_it_be(:guest) { create(:user) } let_it_be(:author) { create(:author) } let_it_be(:assignee) { create(:assignee) } - let(:admin) { create(:user, :admin) } - let(:issue_title) { 'foo' } - let(:issue_description) { 'closed' } - let!(:closed_issue) do + let_it_be(:admin) { create(:user, :admin) } + + let_it_be(:milestone) { create(:milestone, title: '1.0.0', project: project) } + let_it_be(:empty_milestone) do + create(:milestone, title: '2.0.0', project: project) + end + + let(:no_milestone_title) { 'None' } + let(:any_milestone_title) { 'Any' } + + let_it_be(:issue_title) { 'foo' } + let_it_be(:issue_description) { 'closed' } + let_it_be(:closed_issue) do create :closed_issue, author: user, assignees: [user], @@ -31,7 +40,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do closed_at: 1.hour.ago end - let!(:confidential_issue) do + let_it_be(:confidential_issue) do create :issue, :confidential, project: project, @@ -41,7 +50,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do updated_at: 2.hours.ago end - let!(:issue) do + let_it_be(:issue) do create :issue, author: user, assignees: [user], @@ -53,22 +62,12 @@ RSpec.describe API::Issues, feature_category: :team_planning do description: issue_description end - let_it_be(:label) do - create(:label, title: 'label', color: '#FFAABB', project: project) - end + let_it_be(:label) { create(:label, title: 'label', color: '#FFAABB', project: project) } + let_it_be(:label_link) { create(:label_link, label: label, target: issue) } - let!(:label_link) { create(:label_link, label: label, target: issue) } - let(:milestone) { create(:milestone, title: '1.0.0', project: project) } - let_it_be(:empty_milestone) do - create(:milestone, title: '2.0.0', project: project) - end + let_it_be(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) } - let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) } - - let(:no_milestone_title) { 'None' } - let(:any_milestone_title) { 'Any' } - - let!(:merge_request1) do + let_it_be(:merge_request1) do create(:merge_request, :simple, author: user, @@ -77,7 +76,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do description: "closes #{issue.to_reference}") end - let!(:merge_request2) do + let_it_be(:merge_request2) do create(:merge_request, :simple, author: user, @@ -101,7 +100,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do shared_examples 'project issues statistics' do it 'returns project issues statistics' do - get api("/issues_statistics", user), params: params + get api("/projects/#{project.id}/issues_statistics", current_user), params: params expect(response).to have_gitlab_http_status(:ok) expect(json_response['statistics']).not_to be_nil @@ -138,6 +137,8 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'issues_statistics' do + let(:current_user) { nil } + context 'no state is treated as all state' do let(:params) { {} } let(:counts) { { all: 2, closed: 1, opened: 1 } } @@ -534,30 +535,32 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'issues_statistics' do + let(:current_user) { user } + context 'no state is treated as all state' do let(:params) { {} } - let(:counts) { { all: 2, closed: 1, opened: 1 } } + let(:counts) { { all: 3, closed: 1, opened: 2 } } it_behaves_like 'project issues statistics' end context 'statistics when all state is passed' do let(:params) { { state: :all } } - let(:counts) { { all: 2, closed: 1, opened: 1 } } + let(:counts) { { all: 3, closed: 1, opened: 2 } } it_behaves_like 'project issues statistics' end context 'closed state is treated as all state' do let(:params) { { state: :closed } } - let(:counts) { { all: 2, closed: 1, opened: 1 } } + let(:counts) { { all: 3, closed: 1, opened: 2 } } it_behaves_like 'project issues statistics' end context 'opened state is treated as all state' do let(:params) { { state: :opened } } - let(:counts) { { all: 2, closed: 1, opened: 1 } } + let(:counts) { { all: 3, closed: 1, opened: 2 } } it_behaves_like 'project issues statistics' end @@ -592,7 +595,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do context 'sort does not affect statistics ' do let(:params) { { state: :opened, order_by: 'updated_at' } } - let(:counts) { { all: 2, closed: 1, opened: 1 } } + let(:counts) { { all: 3, closed: 1, opened: 2 } } it_behaves_like 'project issues statistics' end diff --git a/spec/requests/api/issues/issues_spec.rb b/spec/requests/api/issues/issues_spec.rb index 94f0443e14a..b89db82b150 100644 --- a/spec/requests/api/issues/issues_spec.rb +++ b/spec/requests/api/issues/issues_spec.rb @@ -145,6 +145,11 @@ RSpec.describe API::Issues, feature_category: :team_planning do let(:result) { issuable.id } end + it_behaves_like 'issuable API rate-limited search' do + let(:url) { '/issues' } + let(:issuable) { issue } + end + it 'returns authentication error without any scope' do get api('/issues') diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 0b69000ae7e..4cd93603c31 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -55,6 +55,11 @@ RSpec.describe API::MergeRequests, feature_category: :source_code_management do let(:issuable) { merge_request } let(:result) { [merge_request_merged.id, merge_request_locked.id, merge_request_closed.id, merge_request.id] } end + + it_behaves_like 'issuable API rate-limited search' do + let(:url) { endpoint_path } + let(:issuable) { merge_request } + end end context 'when authenticated' do @@ -663,6 +668,11 @@ RSpec.describe API::MergeRequests, feature_category: :source_code_management do let(:result) { [merge_request_merged.id, merge_request_locked.id, merge_request_closed.id, merge_request.id] } end + it_behaves_like 'issuable API rate-limited search' do + let(:url) { '/merge_requests' } + let(:issuable) { merge_request } + end + it "returns authentication error without any scope" do get api("/merge_requests") diff --git a/spec/requests/dashboard_controller_spec.rb b/spec/requests/dashboard_controller_spec.rb index 9edacb27c93..1c8ab843ebe 100644 --- a/spec/requests/dashboard_controller_spec.rb +++ b/spec/requests/dashboard_controller_spec.rb @@ -12,4 +12,32 @@ RSpec.describe DashboardController, feature_category: :authentication_and_author let(:url) { issues_dashboard_url(:ics, assignee_username: user.username) } end end + + context 'issues dashboard' do + it_behaves_like 'rate limited endpoint', rate_limit_key: :search_rate_limit do + let_it_be(:current_user) { create(:user) } + + before do + sign_in current_user + end + + def request + get issues_dashboard_path, params: { scope: 'all', search: 'test' } + end + end + end + + context 'merge requests dashboard' do + it_behaves_like 'rate limited endpoint', rate_limit_key: :search_rate_limit do + let_it_be(:current_user) { create(:user) } + + before do + sign_in current_user + end + + def request + get merge_requests_dashboard_path, params: { scope: 'all', search: 'test' } + end + end + end end diff --git a/spec/requests/projects/issues_controller_spec.rb b/spec/requests/projects/issues_controller_spec.rb index bbf200eaacd..a943bd6449c 100644 --- a/spec/requests/projects/issues_controller_spec.rb +++ b/spec/requests/projects/issues_controller_spec.rb @@ -32,6 +32,28 @@ RSpec.describe Projects::IssuesController, feature_category: :team_planning do end end + describe 'GET #index.json' do + let_it_be(:public_project) { create(:project, :public) } + + it_behaves_like 'rate limited endpoint', rate_limit_key: :search_rate_limit do + let_it_be(:current_user) { create(:user) } + + before do + sign_in current_user + end + + def request + get project_issues_path(public_project, format: :json), params: { scope: 'all', search: 'test' } + end + end + + it_behaves_like 'rate limited endpoint', rate_limit_key: :search_rate_limit_unauthenticated do + def request + get project_issues_path(public_project, format: :json), params: { scope: 'all', search: 'test' } + end + end + end + describe 'GET #discussions' do before do login_as(user) diff --git a/spec/requests/projects/merge_requests_controller_spec.rb b/spec/requests/projects/merge_requests_controller_spec.rb index f5f8b5c2d83..aa7a32ef2cf 100644 --- a/spec/requests/projects/merge_requests_controller_spec.rb +++ b/spec/requests/projects/merge_requests_controller_spec.rb @@ -19,6 +19,28 @@ RSpec.describe Projects::MergeRequestsController, feature_category: :source_code end end + describe 'GET #index' do + let_it_be(:public_project) { create(:project, :public) } + + it_behaves_like 'rate limited endpoint', rate_limit_key: :search_rate_limit do + let_it_be(:current_user) { user } + + before do + sign_in current_user + end + + def request + get project_merge_requests_path(public_project), params: { scope: 'all', search: 'test' } + end + end + + it_behaves_like 'rate limited endpoint', rate_limit_key: :search_rate_limit_unauthenticated do + def request + get project_merge_requests_path(public_project), params: { scope: 'all', search: 'test' } + end + end + end + describe 'GET #discussions' do let_it_be(:discussion) { create(:discussion_note_on_merge_request, noteable: merge_request, project: project) } let_it_be(:discussion_reply) { create(:discussion_note_on_merge_request, noteable: merge_request, project: project, in_reply_to: discussion) } diff --git a/spec/services/clusters/aws/authorize_role_service_spec.rb b/spec/services/clusters/aws/authorize_role_service_spec.rb deleted file mode 100644 index 17bbc372675..00000000000 --- a/spec/services/clusters/aws/authorize_role_service_spec.rb +++ /dev/null @@ -1,102 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Clusters::Aws::AuthorizeRoleService do - subject { described_class.new(user, params: params).execute } - - let(:role) { create(:aws_role) } - let(:user) { role.user } - let(:credentials) { instance_double(Aws::Credentials) } - let(:credentials_service) { instance_double(Clusters::Aws::FetchCredentialsService, execute: credentials) } - - let(:role_arn) { 'arn:my-role' } - let(:region) { 'region' } - let(:params) do - params = ActionController::Parameters.new({ - cluster: { - role_arn: role_arn, - region: region - } - }) - - params.require(:cluster).permit(:role_arn, :region) - end - - before do - allow(Clusters::Aws::FetchCredentialsService).to receive(:new) - .with(instance_of(Aws::Role)).and_return(credentials_service) - end - - context 'role exists' do - it 'updates the existing Aws::Role record and returns a set of credentials' do - expect(subject.status).to eq(:ok) - expect(subject.body).to eq(credentials) - expect(role.reload.role_arn).to eq(role_arn) - end - end - - context 'errors' do - shared_examples 'bad request' do - it 'returns an empty hash' do - expect(subject.status).to eq(:unprocessable_entity) - expect(subject.body).to eq({ message: message }) - end - - it 'logs the error' do - expect(::Gitlab::ErrorTracking).to receive(:track_exception) - - subject - end - end - - context 'role does not exist' do - let(:user) { create(:user) } - let(:message) { 'Error: Unable to find AWS role for current user' } - - include_examples 'bad request' - end - - context 'supplied ARN is invalid' do - let(:role_arn) { 'invalid' } - let(:message) { 'Validation failed: Role arn must be a valid Amazon Resource Name' } - - include_examples 'bad request' - end - - context 'client errors' do - before do - allow(credentials_service).to receive(:execute).and_raise(error) - end - - context 'error fetching credentials' do - let(:error) { Aws::STS::Errors::ServiceError.new(nil, 'error message') } - let(:message) { 'AWS service error: error message' } - - include_examples 'bad request' - end - - context 'error in assuming role' do - let(:raw_message) { "User foo is not authorized to perform: sts:AssumeRole on resource bar" } - let(:error) { Aws::STS::Errors::AccessDenied.new(nil, raw_message) } - let(:message) { "Access denied: #{raw_message}" } - - include_examples 'bad request' - end - - context 'credentials not configured' do - let(:error) { Aws::Errors::MissingCredentialsError.new('error message') } - let(:message) { "Error: No AWS credentials were supplied" } - - include_examples 'bad request' - end - - context 'role not configured' do - let(:error) { Clusters::Aws::FetchCredentialsService::MissingRoleError.new('error message') } - let(:message) { "Error: No AWS provision role found for user" } - - include_examples 'bad request' - end - end - end -end diff --git a/spec/services/clusters/aws/fetch_credentials_service_spec.rb b/spec/services/clusters/aws/fetch_credentials_service_spec.rb deleted file mode 100644 index 0358ca1f535..00000000000 --- a/spec/services/clusters/aws/fetch_credentials_service_spec.rb +++ /dev/null @@ -1,139 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Clusters::Aws::FetchCredentialsService do - describe '#execute' do - let(:user) { create(:user) } - let(:provider) { create(:cluster_provider_aws, region: 'ap-southeast-2') } - - let(:gitlab_access_key_id) { 'gitlab-access-key-id' } - let(:gitlab_secret_access_key) { 'gitlab-secret-access-key' } - - let(:gitlab_credentials) { Aws::Credentials.new(gitlab_access_key_id, gitlab_secret_access_key) } - let(:sts_client) { Aws::STS::Client.new(credentials: gitlab_credentials, region: region) } - let(:assumed_role) { instance_double(Aws::AssumeRoleCredentials, credentials: assumed_role_credentials) } - - let(:assumed_role_credentials) { double } - - subject { described_class.new(provision_role, provider: provider).execute } - - context 'provision role is configured' do - let(:provision_role) { create(:aws_role, user: user, region: 'custom-region') } - - before do - stub_application_setting(eks_access_key_id: gitlab_access_key_id) - stub_application_setting(eks_secret_access_key: gitlab_secret_access_key) - - expect(Aws::Credentials).to receive(:new) - .with(gitlab_access_key_id, gitlab_secret_access_key) - .and_return(gitlab_credentials) - - expect(Aws::STS::Client).to receive(:new) - .with(credentials: gitlab_credentials, region: region) - .and_return(sts_client) - - expect(Aws::AssumeRoleCredentials).to receive(:new) - .with( - client: sts_client, - role_arn: provision_role.role_arn, - role_session_name: session_name, - external_id: provision_role.role_external_id, - policy: session_policy - ).and_return(assumed_role) - end - - context 'provider is specified' do - let(:region) { provider.region } - let(:session_name) { "gitlab-eks-cluster-#{provider.cluster_id}-user-#{user.id}" } - let(:session_policy) { nil } - - it { is_expected.to eq assumed_role_credentials } - end - - context 'provider is not specifed' do - let(:provider) { nil } - let(:region) { provision_role.region } - let(:session_name) { "gitlab-eks-autofill-user-#{user.id}" } - let(:session_policy) { 'policy-document' } - - subject { described_class.new(provision_role, provider: provider).execute } - - before do - stub_file_read(Rails.root.join('vendor', 'aws', 'iam', 'eks_cluster_read_only_policy.json'), content: session_policy) - end - - it { is_expected.to eq assumed_role_credentials } - - context 'region is not specifed' do - let(:region) { Clusters::Providers::Aws::DEFAULT_REGION } - let(:provision_role) { create(:aws_role, user: user, region: nil) } - - it { is_expected.to eq assumed_role_credentials } - end - end - end - - context 'provision role is not configured' do - let(:provision_role) { nil } - - it 'raises an error' do - expect { subject }.to raise_error(described_class::MissingRoleError, 'AWS provisioning role not configured') - end - end - - context 'with an instance profile attached to an IAM role' do - let(:sts_client) { Aws::STS::Client.new(region: region, stub_responses: true) } - let(:provision_role) { create(:aws_role, user: user, region: 'custom-region') } - - before do - stub_application_setting(eks_access_key_id: nil) - stub_application_setting(eks_secret_access_key: nil) - - expect(Aws::STS::Client).to receive(:new) - .with(region: region) - .and_return(sts_client) - - expect(Aws::AssumeRoleCredentials).to receive(:new) - .with( - client: sts_client, - role_arn: provision_role.role_arn, - role_session_name: session_name, - external_id: provision_role.role_external_id, - policy: session_policy - ).and_call_original - end - - context 'provider is specified' do - let(:region) { provider.region } - let(:session_name) { "gitlab-eks-cluster-#{provider.cluster_id}-user-#{user.id}" } - let(:session_policy) { nil } - - it 'returns credentials', :aggregate_failures do - expect(subject.access_key_id).to be_present - expect(subject.secret_access_key).to be_present - expect(subject.session_token).to be_present - end - end - - context 'provider is not specifed' do - let(:provider) { nil } - let(:region) { provision_role.region } - let(:session_name) { "gitlab-eks-autofill-user-#{user.id}" } - let(:session_policy) { 'policy-document' } - - before do - stub_file_read(Rails.root.join('vendor', 'aws', 'iam', 'eks_cluster_read_only_policy.json'), content: session_policy) - end - - subject { described_class.new(provision_role, provider: provider).execute } - - it 'returns credentials', :aggregate_failures do - expect(subject.access_key_id).to be_present - expect(subject.secret_access_key).to be_present - expect(subject.session_token).to be_present - end - end - end - end -end diff --git a/spec/services/clusters/aws/finalize_creation_service_spec.rb b/spec/services/clusters/aws/finalize_creation_service_spec.rb deleted file mode 100644 index 6b0cb86eff0..00000000000 --- a/spec/services/clusters/aws/finalize_creation_service_spec.rb +++ /dev/null @@ -1,124 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Clusters::Aws::FinalizeCreationService do - describe '#execute' do - let(:provider) { create(:cluster_provider_aws, :creating) } - let(:platform) { provider.cluster.platform_kubernetes } - - let(:create_service_account_service) { double(execute: true) } - let(:fetch_token_service) { double(execute: gitlab_token) } - let(:kube_client) { double(create_config_map: true) } - let(:cluster_stack) { double(outputs: [endpoint_output, cert_output, node_role_output]) } - let(:node_auth_config_map) { double } - - let(:endpoint_output) { double(output_key: 'ClusterEndpoint', output_value: api_url) } - let(:cert_output) { double(output_key: 'ClusterCertificate', output_value: Base64.encode64(ca_pem)) } - let(:node_role_output) { double(output_key: 'NodeInstanceRole', output_value: node_role) } - - let(:api_url) { 'https://kubernetes.example.com' } - let(:ca_pem) { File.read(Rails.root.join('spec/fixtures/clusters/sample_cert.pem')) } - let(:gitlab_token) { 'gitlab-token' } - let(:iam_token) { 'iam-token' } - let(:node_role) { 'arn::aws::iam::123456789012:role/node-role' } - - subject { described_class.new.execute(provider) } - - before do - allow(Clusters::Kubernetes::CreateOrUpdateServiceAccountService).to receive(:gitlab_creator) - .with(kube_client, rbac: true) - .and_return(create_service_account_service) - - allow(Clusters::Kubernetes::FetchKubernetesTokenService).to receive(:new) - .with( - kube_client, - Clusters::Kubernetes::GITLAB_ADMIN_TOKEN_NAME, - Clusters::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAMESPACE) - .and_return(fetch_token_service) - - allow(Gitlab::Kubernetes::KubeClient).to receive(:new) - .with( - api_url, - auth_options: { bearer_token: iam_token }, - ssl_options: { - verify_ssl: OpenSSL::SSL::VERIFY_PEER, - cert_store: instance_of(OpenSSL::X509::Store) - }, - http_proxy_uri: nil - ) - .and_return(kube_client) - - allow(provider.api_client).to receive(:describe_stacks) - .with(stack_name: provider.cluster.name) - .and_return(double(stacks: [cluster_stack])) - - allow(Kubeclient::AmazonEksCredentials).to receive(:token) - .with(provider.credentials, provider.cluster.name) - .and_return(iam_token) - - allow(Gitlab::Kubernetes::ConfigMaps::AwsNodeAuth).to receive(:new) - .with(node_role).and_return(double(generate: node_auth_config_map)) - end - - it 'configures the provider and platform' do - subject - - expect(provider).to be_created - expect(platform.api_url).to eq(api_url) - expect(platform.ca_pem).to eq(ca_pem) - expect(platform.token).to eq(gitlab_token) - expect(platform).to be_rbac - end - - it 'calls the create_service_account_service' do - expect(create_service_account_service).to receive(:execute).once - - subject - end - - it 'configures cluster node authentication' do - expect(kube_client).to receive(:create_config_map).with(node_auth_config_map).once - - subject - end - - describe 'error handling' do - shared_examples 'provision error' do |message| - it "sets the status to :errored with an appropriate error message" do - subject - - expect(provider).to be_errored - expect(provider.status_reason).to include(message) - end - end - - context 'failed to request stack details from AWS' do - before do - allow(provider.api_client).to receive(:describe_stacks) - .and_raise(Aws::CloudFormation::Errors::ServiceError.new(double, "Error message")) - end - - include_examples 'provision error', 'Failed to fetch CloudFormation stack' - end - - context 'failed to create auth config map' do - before do - allow(kube_client).to receive(:create_config_map) - .and_raise(Kubeclient::HttpError.new(500, 'Error', nil)) - end - - include_examples 'provision error', 'Failed to run Kubeclient' - end - - context 'failed to save records' do - before do - allow(provider.cluster).to receive(:save!) - .and_raise(ActiveRecord::RecordInvalid) - end - - include_examples 'provision error', 'Failed to configure EKS provider' - end - end - end -end diff --git a/spec/services/clusters/aws/provision_service_spec.rb b/spec/services/clusters/aws/provision_service_spec.rb deleted file mode 100644 index 5efac29ec1e..00000000000 --- a/spec/services/clusters/aws/provision_service_spec.rb +++ /dev/null @@ -1,130 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Clusters::Aws::ProvisionService do - describe '#execute' do - let(:provider) { create(:cluster_provider_aws) } - - let(:provision_role) { create(:aws_role, user: provider.created_by_user) } - let(:client) { instance_double(Aws::CloudFormation::Client, create_stack: true) } - let(:cloudformation_template) { double } - let(:credentials) do - instance_double( - Aws::Credentials, - access_key_id: 'key', - secret_access_key: 'secret', - session_token: 'token' - ) - end - - let(:parameters) do - [ - { parameter_key: 'ClusterName', parameter_value: provider.cluster.name }, - { parameter_key: 'ClusterRole', parameter_value: provider.role_arn }, - { parameter_key: 'KubernetesVersion', parameter_value: provider.kubernetes_version }, - { parameter_key: 'ClusterControlPlaneSecurityGroup', parameter_value: provider.security_group_id }, - { parameter_key: 'VpcId', parameter_value: provider.vpc_id }, - { parameter_key: 'Subnets', parameter_value: provider.subnet_ids.join(',') }, - { parameter_key: 'NodeAutoScalingGroupDesiredCapacity', parameter_value: provider.num_nodes.to_s }, - { parameter_key: 'NodeInstanceType', parameter_value: provider.instance_type }, - { parameter_key: 'KeyName', parameter_value: provider.key_name } - ] - end - - subject { described_class.new.execute(provider) } - - before do - allow(Clusters::Aws::FetchCredentialsService).to receive(:new) - .with(provision_role, provider: provider) - .and_return(double(execute: credentials)) - - allow(provider).to receive(:api_client) - .and_return(client) - - stub_file_read(Rails.root.join('vendor', 'aws', 'cloudformation', 'eks_cluster.yaml'), content: cloudformation_template) - end - - it 'updates the provider status to :creating and configures the provider with credentials' do - subject - - expect(provider).to be_creating - expect(provider.access_key_id).to eq 'key' - expect(provider.secret_access_key).to eq 'secret' - expect(provider.session_token).to eq 'token' - end - - it 'creates a CloudFormation stack' do - expect(client).to receive(:create_stack).with( - stack_name: provider.cluster.name, - template_body: cloudformation_template, - parameters: parameters, - capabilities: ["CAPABILITY_IAM"] - ) - - subject - end - - it 'schedules a worker to monitor creation status' do - expect(WaitForClusterCreationWorker).to receive(:perform_in) - .with(Clusters::Aws::VerifyProvisionStatusService::INITIAL_INTERVAL, provider.cluster_id) - - subject - end - - describe 'error handling' do - shared_examples 'provision error' do |message| - it "sets the status to :errored with an appropriate error message" do - subject - - expect(provider).to be_errored - expect(provider.status_reason).to include(message) - end - end - - context 'invalid state transition' do - before do - allow(provider).to receive(:make_creating).and_return(false) - end - - include_examples 'provision error', 'Failed to update provider record' - end - - context 'AWS role is not configured' do - before do - allow(Clusters::Aws::FetchCredentialsService).to receive(:new) - .and_raise(Clusters::Aws::FetchCredentialsService::MissingRoleError) - end - - include_examples 'provision error', 'Amazon role is not configured' - end - - context 'AWS credentials are not configured' do - before do - allow(Clusters::Aws::FetchCredentialsService).to receive(:new) - .and_raise(Aws::Errors::MissingCredentialsError) - end - - include_examples 'provision error', 'Amazon credentials are not configured' - end - - context 'Authentication failure' do - before do - allow(Clusters::Aws::FetchCredentialsService).to receive(:new) - .and_raise(Aws::STS::Errors::ServiceError.new(double, 'Error message')) - end - - include_examples 'provision error', 'Amazon authentication failed' - end - - context 'CloudFormation failure' do - before do - allow(client).to receive(:create_stack) - .and_raise(Aws::CloudFormation::Errors::ServiceError.new(double, 'Error message')) - end - - include_examples 'provision error', 'Amazon CloudFormation request failed' - end - end - end -end diff --git a/spec/services/clusters/aws/verify_provision_status_service_spec.rb b/spec/services/clusters/aws/verify_provision_status_service_spec.rb deleted file mode 100644 index b9a58b97842..00000000000 --- a/spec/services/clusters/aws/verify_provision_status_service_spec.rb +++ /dev/null @@ -1,76 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Clusters::Aws::VerifyProvisionStatusService do - describe '#execute' do - let(:provider) { create(:cluster_provider_aws) } - - let(:stack) { double(stack_status: stack_status, creation_time: creation_time) } - let(:creation_time) { 1.minute.ago } - - subject { described_class.new.execute(provider) } - - before do - allow(provider.api_client).to receive(:describe_stacks) - .with(stack_name: provider.cluster.name) - .and_return(double(stacks: [stack])) - end - - shared_examples 'provision error' do |message| - it "sets the status to :errored with an appropriate error message" do - subject - - expect(provider).to be_errored - expect(provider.status_reason).to include(message) - end - end - - context 'stack creation is still in progress' do - let(:stack_status) { 'CREATE_IN_PROGRESS' } - let(:verify_service) { double(execute: true) } - - it 'schedules a worker to check again later' do - expect(WaitForClusterCreationWorker).to receive(:perform_in) - .with(described_class::POLL_INTERVAL, provider.cluster_id) - - subject - end - - context 'stack creation is taking too long' do - let(:creation_time) { 1.hour.ago } - - include_examples 'provision error', 'Kubernetes cluster creation time exceeds timeout' - end - end - - context 'stack creation is complete' do - let(:stack_status) { 'CREATE_COMPLETE' } - let(:finalize_service) { double(execute: true) } - - it 'finalizes creation' do - expect(Clusters::Aws::FinalizeCreationService).to receive(:new).and_return(finalize_service) - expect(finalize_service).to receive(:execute).with(provider).once - - subject - end - end - - context 'stack creation failed' do - let(:stack_status) { 'CREATE_FAILED' } - - include_examples 'provision error', 'Unexpected status' - end - - context 'error communicating with CloudFormation API' do - let(:stack_status) { 'CREATE_IN_PROGRESS' } - - before do - allow(provider.api_client).to receive(:describe_stacks) - .and_raise(Aws::CloudFormation::Errors::ServiceError.new(double, 'Error message')) - end - - include_examples 'provision error', 'Amazon CloudFormation request failed' - end - end -end diff --git a/spec/services/clusters/create_service_spec.rb b/spec/services/clusters/create_service_spec.rb index 6e252bee7c0..95f10cdbd80 100644 --- a/spec/services/clusters/create_service_spec.rb +++ b/spec/services/clusters/create_service_spec.rb @@ -54,7 +54,6 @@ RSpec.describe Clusters::CreateService do let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, projects: [project]) } it 'creates another cluster' do - expect(ClusterProvisionWorker).to receive(:perform_async) expect { subject }.to change { Clusters::Cluster.count }.by(1) end end diff --git a/spec/services/clusters/gcp/fetch_operation_service_spec.rb b/spec/services/clusters/gcp/fetch_operation_service_spec.rb deleted file mode 100644 index 990cc745382..00000000000 --- a/spec/services/clusters/gcp/fetch_operation_service_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Clusters::Gcp::FetchOperationService do - include GoogleApi::CloudPlatformHelpers - - describe '#execute' do - let(:provider) { create(:cluster_provider_gcp, :creating) } - let(:gcp_project_id) { provider.gcp_project_id } - let(:zone) { provider.zone } - let(:operation_id) { provider.operation_id } - - shared_examples 'success' do - it 'yields' do - expect { |b| described_class.new.execute(provider, &b) } - .to yield_with_args - end - end - - shared_examples 'error' do - it 'sets an error to provider object' do - expect { |b| described_class.new.execute(provider, &b) } - .not_to yield_with_args - expect(provider.reload).to be_errored - end - end - - context 'when succeeded to fetch operation' do - before do - stub_cloud_platform_get_zone_operation(gcp_project_id, zone, operation_id) - end - - it_behaves_like 'success' - end - - context 'when Internal Server Error happened' do - before do - stub_cloud_platform_get_zone_operation_error(gcp_project_id, zone, operation_id) - end - - it_behaves_like 'error' - end - end -end diff --git a/spec/services/clusters/gcp/finalize_creation_service_spec.rb b/spec/services/clusters/gcp/finalize_creation_service_spec.rb deleted file mode 100644 index 9c553d0eec2..00000000000 --- a/spec/services/clusters/gcp/finalize_creation_service_spec.rb +++ /dev/null @@ -1,161 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Clusters::Gcp::FinalizeCreationService, '#execute' do - include GoogleApi::CloudPlatformHelpers - include KubernetesHelpers - - let(:cluster) { create(:cluster, :project, :providing_by_gcp) } - let(:provider) { cluster.provider } - let(:platform) { cluster.platform } - let(:endpoint) { '111.111.111.111' } - let(:api_url) { 'https://' + endpoint } - let(:secret_name) { 'gitlab-token' } - let(:token) { 'sample-token' } - let(:namespace) { "#{cluster.project.path}-#{cluster.project.id}" } - - subject { described_class.new.execute(provider) } - - shared_examples 'success' do - it 'configures provider and kubernetes' do - subject - - expect(provider).to be_created - end - - it 'properly configures database models' do - subject - - cluster.reload - - expect(provider.endpoint).to eq(endpoint) - expect(platform.api_url).to eq(api_url) - expect(platform.ca_cert).to eq(Base64.decode64(load_sample_cert).strip) - expect(platform.token).to eq(token) - end - end - - shared_examples 'error' do - it 'sets an error to provider object' do - subject - - expect(provider.reload).to be_errored - end - end - - shared_examples 'kubernetes information not successfully fetched' do - context 'when failed to fetch gke cluster info' do - before do - stub_cloud_platform_get_zone_cluster_error(provider.gcp_project_id, provider.zone, cluster.name) - end - - it_behaves_like 'error' - end - - context 'when token is empty' do - let(:token) { '' } - - it_behaves_like 'error' - end - - context 'when failed to fetch kubernetes token' do - before do - stub_kubeclient_get_secret_error(api_url, secret_name, namespace: 'default') - end - - it_behaves_like 'error' - end - - context 'when service account fails to create' do - before do - stub_kubeclient_create_service_account_error(api_url, namespace: 'default') - end - - it_behaves_like 'error' - end - end - - shared_context 'kubernetes information successfully fetched' do - before do - stub_cloud_platform_get_zone_cluster( - provider.gcp_project_id, provider.zone, cluster.name, { endpoint: endpoint } - ) - - stub_kubeclient_discover(api_url) - stub_kubeclient_get_namespace(api_url) - stub_kubeclient_create_namespace(api_url) - stub_kubeclient_get_service_account_error(api_url, 'gitlab') - stub_kubeclient_create_service_account(api_url) - stub_kubeclient_create_secret(api_url) - stub_kubeclient_put_secret(api_url, 'gitlab-token') - - stub_kubeclient_get_secret( - api_url, - metadata_name: secret_name, - token: Base64.encode64(token), - namespace: 'default' - ) - - stub_kubeclient_put_cluster_role_binding(api_url, 'gitlab-admin') - end - end - - context 'With a legacy ABAC cluster' do - before do - provider.legacy_abac = true - end - - include_context 'kubernetes information successfully fetched' - - it_behaves_like 'success' - - it 'uses ABAC authorization type' do - subject - cluster.reload - - expect(platform).to be_abac - expect(platform.authorization_type).to eq('abac') - end - - it_behaves_like 'kubernetes information not successfully fetched' - end - - context 'With an RBAC cluster' do - before do - provider.legacy_abac = false - end - - include_context 'kubernetes information successfully fetched' - - it_behaves_like 'success' - - it 'uses RBAC authorization type' do - subject - cluster.reload - - expect(platform).to be_rbac - expect(platform.authorization_type).to eq('rbac') - end - - it_behaves_like 'kubernetes information not successfully fetched' - end - - context 'With a Cloud Run cluster' do - before do - provider.cloud_run = true - end - - include_context 'kubernetes information successfully fetched' - - it_behaves_like 'success' - - it 'has knative pre-installed' do - subject - cluster.reload - - expect(cluster.application_knative).to be_present - expect(cluster.application_knative).to be_pre_installed - end - end -end diff --git a/spec/services/clusters/gcp/provision_service_spec.rb b/spec/services/clusters/gcp/provision_service_spec.rb deleted file mode 100644 index c8b7f628e5b..00000000000 --- a/spec/services/clusters/gcp/provision_service_spec.rb +++ /dev/null @@ -1,71 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Clusters::Gcp::ProvisionService do - include GoogleApi::CloudPlatformHelpers - - describe '#execute' do - let(:provider) { create(:cluster_provider_gcp, :scheduled) } - let(:gcp_project_id) { provider.gcp_project_id } - let(:zone) { provider.zone } - - shared_examples 'success' do - it 'schedules a worker for status minitoring' do - expect(WaitForClusterCreationWorker).to receive(:perform_in) - - described_class.new.execute(provider) - - expect(provider.reload).to be_creating - end - end - - shared_examples 'error' do - it 'sets an error to provider object' do - described_class.new.execute(provider) - - expect(provider.reload).to be_errored - end - end - - context 'when succeeded to request provision' do - before do - stub_cloud_platform_create_cluster(gcp_project_id, zone) - end - - it_behaves_like 'success' - end - - context 'when operation status is unexpected' do - before do - stub_cloud_platform_create_cluster( - gcp_project_id, zone, - { - "status": 'unexpected' - }) - end - - it_behaves_like 'error' - end - - context 'when selfLink is unexpected' do - before do - stub_cloud_platform_create_cluster( - gcp_project_id, zone, - { - "selfLink": 'unexpected' - }) - end - - it_behaves_like 'error' - end - - context 'when Internal Server Error happened' do - before do - stub_cloud_platform_create_cluster_error(gcp_project_id, zone) - end - - it_behaves_like 'error' - end - end -end diff --git a/spec/services/clusters/gcp/verify_provision_status_service_spec.rb b/spec/services/clusters/gcp/verify_provision_status_service_spec.rb deleted file mode 100644 index ffe4516c02b..00000000000 --- a/spec/services/clusters/gcp/verify_provision_status_service_spec.rb +++ /dev/null @@ -1,111 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Clusters::Gcp::VerifyProvisionStatusService do - include GoogleApi::CloudPlatformHelpers - - describe '#execute' do - let(:provider) { create(:cluster_provider_gcp, :creating) } - let(:gcp_project_id) { provider.gcp_project_id } - let(:zone) { provider.zone } - let(:operation_id) { provider.operation_id } - - shared_examples 'continue_creation' do - it 'schedules a worker for status minitoring' do - expect(WaitForClusterCreationWorker).to receive(:perform_in) - - described_class.new.execute(provider) - end - end - - shared_examples 'finalize_creation' do - it 'schedules a worker for status minitoring' do - expect_next_instance_of(Clusters::Gcp::FinalizeCreationService) do |instance| - expect(instance).to receive(:execute) - end - - described_class.new.execute(provider) - end - end - - shared_examples 'error' do - it 'sets an error to provider object' do - described_class.new.execute(provider) - - expect(provider.reload).to be_errored - end - end - - context 'when operation status is RUNNING' do - before do - stub_cloud_platform_get_zone_operation( - gcp_project_id, zone, operation_id, - { - "status": 'RUNNING', - "startTime": 1.minute.ago.strftime("%FT%TZ") - }) - end - - it_behaves_like 'continue_creation' - - context 'when cluster creation time exceeds timeout' do - before do - stub_cloud_platform_get_zone_operation( - gcp_project_id, zone, operation_id, - { - "status": 'RUNNING', - "startTime": 30.minutes.ago.strftime("%FT%TZ") - }) - end - - it_behaves_like 'error' - end - end - - context 'when operation status is PENDING' do - before do - stub_cloud_platform_get_zone_operation( - gcp_project_id, zone, operation_id, - { - "status": 'PENDING', - "startTime": 1.minute.ago.strftime("%FT%TZ") - }) - end - - it_behaves_like 'continue_creation' - end - - context 'when operation status is DONE' do - before do - stub_cloud_platform_get_zone_operation( - gcp_project_id, zone, operation_id, - { - "status": 'DONE' - }) - end - - it_behaves_like 'finalize_creation' - end - - context 'when operation status is unexpected' do - before do - stub_cloud_platform_get_zone_operation( - gcp_project_id, zone, operation_id, - { - "status": 'unexpected' - }) - end - - it_behaves_like 'error' - end - - context 'when failed to get operation status' do - before do - stub_cloud_platform_get_zone_operation_error(gcp_project_id, zone, operation_id) - end - - it_behaves_like 'error' - end - end -end diff --git a/spec/support/services/clusters/create_service_shared.rb b/spec/support/services/clusters/create_service_shared.rb index f8a58a828ce..80fa7c58515 100644 --- a/spec/support/services/clusters/create_service_shared.rb +++ b/spec/support/services/clusters/create_service_shared.rb @@ -37,9 +37,7 @@ RSpec.shared_context 'invalid cluster create params' do end RSpec.shared_examples 'create cluster service success' do - it 'creates a cluster object and performs a worker' do - expect(ClusterProvisionWorker).to receive(:perform_async) - + it 'creates a cluster object' do expect { subject } .to change { Clusters::Cluster.count }.by(1) .and change { Clusters::Providers::Gcp.count }.by(1) @@ -60,7 +58,6 @@ end RSpec.shared_examples 'create cluster service error' do it 'returns an error' do - expect(ClusterProvisionWorker).not_to receive(:perform_async) expect { subject }.to change { Clusters::Cluster.count }.by(0) expect(subject.errors[:"provider_gcp.gcp_project_id"]).to be_present end diff --git a/spec/support/shared_examples/controllers/issuables_list_metadata_shared_examples.rb b/spec/support/shared_examples/controllers/issuables_list_metadata_shared_examples.rb index 02915206cc5..446bc4cd92f 100644 --- a/spec/support/shared_examples/controllers/issuables_list_metadata_shared_examples.rb +++ b/spec/support/shared_examples/controllers/issuables_list_metadata_shared_examples.rb @@ -42,6 +42,10 @@ RSpec.shared_examples 'issuables list meta-data' do |issuable_type, action = nil let(:result_issuable) { issuables.first } let(:search) { result_issuable.title } + before do + stub_application_setting(search_rate_limit: 0, search_rate_limit_unauthenticated: 0) + end + # .simple_sorts is the same across all Sortable classes sorts = ::Issue.simple_sorts.keys + %w[popularity priority label_priority] sorts.each do |sort| diff --git a/spec/support/shared_examples/controllers/rate_limited_endpoint_shared_examples.rb b/spec/support/shared_examples/controllers/rate_limited_endpoint_shared_examples.rb index 20edca1ee9f..b34038ca0e4 100644 --- a/spec/support/shared_examples/controllers/rate_limited_endpoint_shared_examples.rb +++ b/spec/support/shared_examples/controllers/rate_limited_endpoint_shared_examples.rb @@ -5,7 +5,9 @@ # - current_user # - error_message # optional -RSpec.shared_examples 'rate limited endpoint' do |rate_limit_key:| +RSpec.shared_examples 'rate limited endpoint' do |rate_limit_key:, graphql: false| + let(:error_message) { _('This endpoint has been requested too many times. Try again later.') } + context 'when rate limiter enabled', :freeze_time, :clean_gitlab_redis_rate_limiting do let(:expected_logger_attributes) do { @@ -25,8 +27,6 @@ RSpec.shared_examples 'rate limited endpoint' do |rate_limit_key:| end end - let(:error_message) { _('This endpoint has been requested too many times. Try again later.') } - before do allow(Gitlab::ApplicationRateLimiter).to receive(:threshold).with(rate_limit_key).and_return(1) end @@ -37,12 +37,16 @@ RSpec.shared_examples 'rate limited endpoint' do |rate_limit_key:| request request - expect(response).to have_gitlab_http_status(:too_many_requests) + if graphql + expect_graphql_errors_to_include(error_message) + else + expect(response).to have_gitlab_http_status(:too_many_requests) - if example.metadata[:type] == :controller - expect(response.body).to eq(error_message) - else # it is API spec - expect(response.body).to eq({ message: { error: error_message } }.to_json) + if response.content_type == 'application/json' # it is API spec + expect(response.body).to eq({ message: { error: error_message } }.to_json) + else + expect(response.body).to eq(error_message) + end end end end @@ -57,7 +61,11 @@ RSpec.shared_examples 'rate limited endpoint' do |rate_limit_key:| request - expect(response).not_to have_gitlab_http_status(:too_many_requests) + if graphql + expect_graphql_errors_to_be_empty + else + expect(response).not_to have_gitlab_http_status(:too_many_requests) + end end end end diff --git a/spec/support/shared_examples/requests/api/issuable_search_shared_examples.rb b/spec/support/shared_examples/requests/api/issuable_search_shared_examples.rb index 9f67bd69560..fcde3b65b4f 100644 --- a/spec/support/shared_examples/requests/api/issuable_search_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/issuable_search_shared_examples.rb @@ -34,3 +34,35 @@ RSpec.shared_examples 'issuable anonymous search' do end end end + +RSpec.shared_examples 'issuable API rate-limited search' do + it_behaves_like 'rate limited endpoint', rate_limit_key: :search_rate_limit do + let(:current_user) { user } + + def request + get api(url, current_user), params: { scope: 'all', search: issuable.title } + end + end + + it_behaves_like 'rate limited endpoint', rate_limit_key: :search_rate_limit_unauthenticated do + def request + get api(url), params: { scope: 'all', search: issuable.title } + end + end + + context 'when rate_limit_issuable_searches is disabled', :freeze_time, :clean_gitlab_redis_rate_limiting do + before do + stub_feature_flags(rate_limit_issuable_searches: false) + + allow(Gitlab::ApplicationRateLimiter).to receive(:threshold) + .with(:search_rate_limit_unauthenticated).and_return(1) + end + + it 'does not enforce the rate limit' do + get api(url), params: { scope: 'all', search: issuable.title } + get api(url), params: { scope: 'all', search: issuable.title } + + expect(response).to have_gitlab_http_status(:ok) + end + end +end diff --git a/spec/workers/cluster_provision_worker_spec.rb b/spec/workers/cluster_provision_worker_spec.rb deleted file mode 100644 index 2d6ca4ab7e3..00000000000 --- a/spec/workers/cluster_provision_worker_spec.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe ClusterProvisionWorker do - describe '#perform' do - context 'when provider type is gcp' do - let(:cluster) { create(:cluster, provider_type: :gcp, provider_gcp: provider) } - let(:provider) { create(:cluster_provider_gcp, :scheduled) } - - it 'provision a cluster' do - expect_any_instance_of(Clusters::Gcp::ProvisionService).to receive(:execute).with(provider) - - described_class.new.perform(cluster.id) - end - end - - context 'when provider type is aws' do - let(:cluster) { create(:cluster, provider_type: :aws, provider_aws: provider) } - let(:provider) { create(:cluster_provider_aws, :scheduled) } - - it 'provision a cluster' do - expect_any_instance_of(Clusters::Aws::ProvisionService).to receive(:execute).with(provider) - - described_class.new.perform(cluster.id) - end - end - - context 'when provider type is user' do - let(:cluster) { create(:cluster, :provided_by_user) } - - it 'does not provision a cluster' do - expect_any_instance_of(Clusters::Gcp::ProvisionService).not_to receive(:execute) - - described_class.new.perform(cluster.id) - end - end - - context 'when cluster does not exist' do - it 'does not provision a cluster' do - expect_any_instance_of(Clusters::Gcp::ProvisionService).not_to receive(:execute) - - described_class.new.perform(123) - end - end - end -end diff --git a/spec/workers/wait_for_cluster_creation_worker_spec.rb b/spec/workers/wait_for_cluster_creation_worker_spec.rb deleted file mode 100644 index 9079dff1afe..00000000000 --- a/spec/workers/wait_for_cluster_creation_worker_spec.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe WaitForClusterCreationWorker do - describe '#perform' do - context 'when provider type is gcp' do - let(:cluster) { create(:cluster, provider_type: :gcp, provider_gcp: provider) } - let(:provider) { create(:cluster_provider_gcp, :creating) } - - it 'provisions a cluster' do - expect_any_instance_of(Clusters::Gcp::VerifyProvisionStatusService).to receive(:execute).with(provider) - - described_class.new.perform(cluster.id) - end - end - - context 'when provider type is aws' do - let(:cluster) { create(:cluster, provider_type: :aws, provider_aws: provider) } - let(:provider) { create(:cluster_provider_aws, :creating) } - - it 'provisions a cluster' do - expect_any_instance_of(Clusters::Aws::VerifyProvisionStatusService).to receive(:execute).with(provider) - - described_class.new.perform(cluster.id) - end - end - - context 'when provider type is user' do - let(:cluster) { create(:cluster, provider_type: :user) } - - it 'does not provision a cluster' do - expect_any_instance_of(Clusters::Gcp::VerifyProvisionStatusService).not_to receive(:execute) - - described_class.new.perform(cluster.id) - end - end - - context 'when cluster does not exist' do - it 'does not provision a cluster' do - expect_any_instance_of(Clusters::Gcp::VerifyProvisionStatusService).not_to receive(:execute) - - described_class.new.perform(123) - end - end - end -end |