summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-12-22 03:09:39 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-12-22 03:09:39 +0000
commitf8edcff7e9aff93f8ac605c19e542204b0ed9ba2 (patch)
treefe45e8bc69f5c68c6d4ee7505a4d61c4fdb70299 /spec
parentd61d19da54b0fb8fd54df4007fa95cd39db17e57 (diff)
downloadgitlab-ce-f8edcff7e9aff93f8ac605c19e542204b0ed9ba2.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/admin/clusters_controller_spec.rb4
-rw-r--r--spec/controllers/concerns/check_rate_limit_spec.rb4
-rw-r--r--spec/controllers/groups/clusters_controller_spec.rb4
-rw-r--r--spec/controllers/projects/clusters_controller_spec.rb4
-rw-r--r--spec/frontend/autosave_spec.js128
-rw-r--r--spec/frontend/batch_comments/components/submit_dropdown_spec.js2
-rw-r--r--spec/frontend/design_management/components/design_notes/design_reply_form_spec.js24
-rw-r--r--spec/frontend/diffs/components/diff_line_note_form_spec.js3
-rw-r--r--spec/frontend/issuable/issuable_form_spec.js10
-rw-r--r--spec/frontend/notes/components/comment_form_spec.js9
-rw-r--r--spec/frontend/notes/components/note_body_spec.js12
-rw-r--r--spec/frontend/vue_shared/components/listbox_input/listbox_input_spec.js36
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js17
-rw-r--r--spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js24
-rw-r--r--spec/lib/api/helpers/rate_limiter_spec.rb4
-rw-r--r--spec/lib/gitlab/application_rate_limiter_spec.rb46
-rw-r--r--spec/models/clusters/providers/aws_spec.rb33
-rw-r--r--spec/models/clusters/providers/gcp_spec.rb25
-rw-r--r--spec/requests/api/graphql/ci/jobs_spec.rb8
-rw-r--r--spec/requests/api/graphql/issues_spec.rb26
-rw-r--r--spec/requests/api/issues/get_project_issues_spec.rb61
-rw-r--r--spec/requests/api/issues/issues_spec.rb5
-rw-r--r--spec/requests/api/merge_requests_spec.rb10
-rw-r--r--spec/requests/dashboard_controller_spec.rb28
-rw-r--r--spec/requests/projects/issues_controller_spec.rb22
-rw-r--r--spec/requests/projects/merge_requests_controller_spec.rb22
-rw-r--r--spec/services/clusters/aws/authorize_role_service_spec.rb102
-rw-r--r--spec/services/clusters/aws/fetch_credentials_service_spec.rb139
-rw-r--r--spec/services/clusters/aws/finalize_creation_service_spec.rb124
-rw-r--r--spec/services/clusters/aws/provision_service_spec.rb130
-rw-r--r--spec/services/clusters/aws/verify_provision_status_service_spec.rb76
-rw-r--r--spec/services/clusters/create_service_spec.rb1
-rw-r--r--spec/services/clusters/gcp/fetch_operation_service_spec.rb45
-rw-r--r--spec/services/clusters/gcp/finalize_creation_service_spec.rb161
-rw-r--r--spec/services/clusters/gcp/provision_service_spec.rb71
-rw-r--r--spec/services/clusters/gcp/verify_provision_status_service_spec.rb111
-rw-r--r--spec/support/services/clusters/create_service_shared.rb5
-rw-r--r--spec/support/shared_examples/controllers/issuables_list_metadata_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/controllers/rate_limited_endpoint_shared_examples.rb26
-rw-r--r--spec/support/shared_examples/requests/api/issuable_search_shared_examples.rb32
-rw-r--r--spec/workers/cluster_provision_worker_spec.rb47
-rw-r--r--spec/workers/wait_for_cluster_creation_worker_spec.rb47
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