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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-08-10 18:10:03 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-08-10 18:10:03 +0300
commitc74c13e2e1f3287e98f2519b098180bb30d358af (patch)
treed410b27b793c04ed5e909edf555b99b65214c8f5 /spec
parent63c306d96043ff012510358037c19053a2102e8a (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/frontend/content_editor/components/__snapshots__/toolbar_link_button_spec.js.snap17
-rw-r--r--spec/frontend/content_editor/components/toolbar_image_button_spec.js8
-rw-r--r--spec/frontend/content_editor/components/toolbar_link_button_spec.js36
-rw-r--r--spec/frontend/content_editor/extensions/attachment_spec.js235
-rw-r--r--spec/frontend/content_editor/extensions/image_spec.js193
-rw-r--r--spec/frontend/content_editor/services/create_content_editor_spec.js4
-rw-r--r--spec/frontend/content_editor/services/upload_helpers_spec.js (renamed from spec/frontend/content_editor/services/upload_file_spec.js)4
-rw-r--r--spec/frontend/notes/components/noteable_note_spec.js27
-rw-r--r--spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js28
-rw-r--r--spec/frontend/reports/codequality_report/store/actions_spec.js11
-rw-r--r--spec/frontend/reports/codequality_report/store/getters_spec.js7
-rw-r--r--spec/frontend/reports/codequality_report/store/mutations_spec.js17
-rw-r--r--spec/frontend/vue_mr_widget/mock_data.js5
-rw-r--r--spec/helpers/groups_helper_spec.rb36
-rw-r--r--spec/lib/gitlab/database/connection_spec.rb36
-rw-r--r--spec/lib/gitlab/database/load_balancing/host_list_spec.rb46
-rw-r--r--spec/lib/gitlab/database/load_balancing/host_spec.rb15
-rw-r--r--spec/lib/gitlab/database/load_balancing/load_balancer_spec.rb58
-rw-r--r--spec/lib/gitlab/database/load_balancing/service_discovery_spec.rb2
-rw-r--r--spec/lib/gitlab/database/load_balancing_spec.rb4
-rw-r--r--spec/lib/gitlab/usage/metric_definition_spec.rb8
-rw-r--r--spec/lib/gitlab/usage/metric_spec.rb51
-rw-r--r--spec/lib/sidebars/groups/menus/packages_registries_menu_spec.rb163
-rw-r--r--spec/requests/api/graphql/ci/runner_spec.rb38
-rw-r--r--spec/requests/projects/merge_requests/diffs_spec.rb72
-rw-r--r--spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb26
26 files changed, 751 insertions, 396 deletions
diff --git a/spec/frontend/content_editor/components/__snapshots__/toolbar_link_button_spec.js.snap b/spec/frontend/content_editor/components/__snapshots__/toolbar_link_button_spec.js.snap
index e56c37b0dc9..3c88c05a4b4 100644
--- a/spec/frontend/content_editor/components/__snapshots__/toolbar_link_button_spec.js.snap
+++ b/spec/frontend/content_editor/components/__snapshots__/toolbar_link_button_spec.js.snap
@@ -26,8 +26,21 @@ exports[`content_editor/components/toolbar_link_button renders dropdown componen
</div>
</form>
</li>
- <!---->
- <!---->
+ <li role=\\"presentation\\" class=\\"gl-new-dropdown-divider\\">
+ <hr role=\\"separator\\" aria-orientation=\\"horizontal\\" class=\\"dropdown-divider\\">
+ </li>
+ <li role=\\"presentation\\" class=\\"gl-new-dropdown-item\\"><button role=\\"menuitem\\" type=\\"button\\" class=\\"dropdown-item\\">
+ <!---->
+ <!---->
+ <!---->
+ <div class=\\"gl-new-dropdown-item-text-wrapper\\">
+ <p class=\\"gl-new-dropdown-item-text-primary\\">
+ Upload file
+ </p>
+ <!---->
+ </div>
+ <!---->
+ </button></li> <input type=\\"file\\" name=\\"content_editor_attachment\\" class=\\"gl-display-none\\">
</div>
<!---->
</div>
diff --git a/spec/frontend/content_editor/components/toolbar_image_button_spec.js b/spec/frontend/content_editor/components/toolbar_image_button_spec.js
index bf744d5d385..dab7e67d7c5 100644
--- a/spec/frontend/content_editor/components/toolbar_image_button_spec.js
+++ b/spec/frontend/content_editor/components/toolbar_image_button_spec.js
@@ -1,6 +1,7 @@
import { GlButton, GlFormInputGroup } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import ToolbarImageButton from '~/content_editor/components/toolbar_image_button.vue';
+import Attachment from '~/content_editor/extensions/attachment';
import Image from '~/content_editor/extensions/image';
import { createTestEditor, mockChainedCommands } from '../test_utils';
@@ -31,7 +32,8 @@ describe('content_editor/components/toolbar_image_button', () => {
beforeEach(() => {
editor = createTestEditor({
extensions: [
- Image.configure({
+ Image,
+ Attachment.configure({
renderMarkdown: jest.fn(),
uploadsPath: '/uploads/',
}),
@@ -64,13 +66,13 @@ describe('content_editor/components/toolbar_image_button', () => {
});
it('uploads the selected image when file input changes', async () => {
- const commands = mockChainedCommands(editor, ['focus', 'uploadImage', 'run']);
+ const commands = mockChainedCommands(editor, ['focus', 'uploadAttachment', 'run']);
const file = new File(['foo'], 'foo.png', { type: 'image/png' });
await selectFile(file);
expect(commands.focus).toHaveBeenCalled();
- expect(commands.uploadImage).toHaveBeenCalledWith({ file });
+ expect(commands.uploadAttachment).toHaveBeenCalledWith({ file });
expect(commands.run).toHaveBeenCalled();
expect(wrapper.emitted().execute[0]).toEqual([{ contentType: 'image', value: 'upload' }]);
diff --git a/spec/frontend/content_editor/components/toolbar_link_button_spec.js b/spec/frontend/content_editor/components/toolbar_link_button_spec.js
index 405213d0487..0cf488260bd 100644
--- a/spec/frontend/content_editor/components/toolbar_link_button_spec.js
+++ b/spec/frontend/content_editor/components/toolbar_link_button_spec.js
@@ -1,4 +1,4 @@
-import { GlDropdown, GlDropdownDivider, GlButton, GlFormInputGroup } from '@gitlab/ui';
+import { GlDropdown, GlButton, GlFormInputGroup } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import ToolbarLinkButton from '~/content_editor/components/toolbar_link_button.vue';
import Link from '~/content_editor/extensions/link';
@@ -19,11 +19,18 @@ describe('content_editor/components/toolbar_link_button', () => {
});
};
const findDropdown = () => wrapper.findComponent(GlDropdown);
- const findDropdownDivider = () => wrapper.findComponent(GlDropdownDivider);
const findLinkURLInput = () => wrapper.findComponent(GlFormInputGroup).find('input[type="text"]');
const findApplyLinkButton = () => wrapper.findComponent(GlButton);
const findRemoveLinkButton = () => wrapper.findByText('Remove link');
+ const selectFile = async (file) => {
+ const input = wrapper.find({ ref: 'fileSelector' });
+
+ // override the property definition because `input.files` isn't directly modifyable
+ Object.defineProperty(input.element, 'files', { value: [file], writable: true });
+ await input.trigger('change');
+ };
+
beforeEach(() => {
editor = createTestEditor();
});
@@ -51,8 +58,11 @@ describe('content_editor/components/toolbar_link_button', () => {
expect(findDropdown().props('toggleClass')).toEqual({ active: true });
});
+ it('does not display the upload file option', () => {
+ expect(wrapper.findByText('Upload file').exists()).toBe(false);
+ });
+
it('displays a remove link dropdown option', () => {
- expect(findDropdownDivider().exists()).toBe(true);
expect(wrapper.findByText('Remove link').exists()).toBe(true);
});
@@ -107,7 +117,7 @@ describe('content_editor/components/toolbar_link_button', () => {
});
});
- describe('when there is not an active link', () => {
+ describe('when there is no active link', () => {
beforeEach(() => {
jest.spyOn(editor, 'isActive');
editor.isActive.mockReturnValueOnce(false);
@@ -118,8 +128,11 @@ describe('content_editor/components/toolbar_link_button', () => {
expect(findDropdown().props('toggleClass')).toEqual({ active: false });
});
+ it('displays the upload file option', () => {
+ expect(wrapper.findByText('Upload file').exists()).toBe(true);
+ });
+
it('does not display a remove link dropdown option', () => {
- expect(findDropdownDivider().exists()).toBe(false);
expect(wrapper.findByText('Remove link').exists()).toBe(false);
});
@@ -138,6 +151,19 @@ describe('content_editor/components/toolbar_link_button', () => {
expect(wrapper.emitted().execute[0]).toEqual([{ contentType: 'link' }]);
});
+
+ it('uploads the selected image when file input changes', async () => {
+ const commands = mockChainedCommands(editor, ['focus', 'uploadAttachment', 'run']);
+ const file = new File(['foo'], 'foo.png', { type: 'image/png' });
+
+ await selectFile(file);
+
+ expect(commands.focus).toHaveBeenCalled();
+ expect(commands.uploadAttachment).toHaveBeenCalledWith({ file });
+ expect(commands.run).toHaveBeenCalled();
+
+ expect(wrapper.emitted().execute[0]).toEqual([{ contentType: 'link' }]);
+ });
});
describe('when the user displays the dropdown', () => {
diff --git a/spec/frontend/content_editor/extensions/attachment_spec.js b/spec/frontend/content_editor/extensions/attachment_spec.js
new file mode 100644
index 00000000000..d87a1459b50
--- /dev/null
+++ b/spec/frontend/content_editor/extensions/attachment_spec.js
@@ -0,0 +1,235 @@
+import axios from 'axios';
+import MockAdapter from 'axios-mock-adapter';
+import { once } from 'lodash';
+import waitForPromises from 'helpers/wait_for_promises';
+import Attachment from '~/content_editor/extensions/attachment';
+import Image from '~/content_editor/extensions/image';
+import Link from '~/content_editor/extensions/link';
+import Loading from '~/content_editor/extensions/loading';
+import httpStatus from '~/lib/utils/http_status';
+import { loadMarkdownApiResult } from '../markdown_processing_examples';
+import { createTestEditor, createDocBuilder } from '../test_utils';
+
+describe('content_editor/extensions/image', () => {
+ let tiptapEditor;
+ let eq;
+ let doc;
+ let p;
+ let image;
+ let loading;
+ let link;
+ let renderMarkdown;
+ let mock;
+
+ const uploadsPath = '/uploads/';
+ const imageFile = new File(['foo'], 'test-file.png', { type: 'image/png' });
+ const attachmentFile = new File(['foo'], 'test-file.zip', { type: 'application/zip' });
+
+ beforeEach(() => {
+ renderMarkdown = jest.fn();
+
+ tiptapEditor = createTestEditor({
+ extensions: [Loading, Link, Image, Attachment.configure({ renderMarkdown, uploadsPath })],
+ });
+
+ ({
+ builders: { doc, p, image, loading, link },
+ eq,
+ } = createDocBuilder({
+ tiptapEditor,
+ names: {
+ loading: { markType: Loading.name },
+ image: { nodeType: Image.name },
+ link: { nodeType: Link.name },
+ },
+ }));
+
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.reset();
+ });
+
+ it.each`
+ eventType | propName | eventData | output
+ ${'paste'} | ${'handlePaste'} | ${{ clipboardData: { files: [attachmentFile] } }} | ${true}
+ ${'paste'} | ${'handlePaste'} | ${{ clipboardData: { files: [] } }} | ${undefined}
+ ${'drop'} | ${'handleDrop'} | ${{ dataTransfer: { files: [attachmentFile] } }} | ${true}
+ `('handles $eventType properly', ({ eventType, propName, eventData, output }) => {
+ const event = Object.assign(new Event(eventType), eventData);
+ const handled = tiptapEditor.view.someProp(propName, (eventHandler) => {
+ return eventHandler(tiptapEditor.view, event);
+ });
+
+ expect(handled).toBe(output);
+ });
+
+ describe('uploadAttachment command', () => {
+ let initialDoc;
+ beforeEach(() => {
+ initialDoc = doc(p(''));
+ tiptapEditor.commands.setContent(initialDoc.toJSON());
+ });
+
+ describe('when the file has image mime type', () => {
+ const base64EncodedFile = 'data:image/png;base64,Zm9v';
+
+ beforeEach(() => {
+ renderMarkdown.mockResolvedValue(
+ loadMarkdownApiResult('project_wiki_attachment_image').body,
+ );
+ });
+
+ describe('when uploading succeeds', () => {
+ const successResponse = {
+ link: {
+ markdown: '![test-file](test-file.png)',
+ },
+ };
+
+ beforeEach(() => {
+ mock.onPost().reply(httpStatus.OK, successResponse);
+ });
+
+ it('inserts an image with src set to the encoded image file and uploading true', (done) => {
+ const expectedDoc = doc(p(image({ uploading: true, src: base64EncodedFile })));
+
+ tiptapEditor.on(
+ 'update',
+ once(() => {
+ expect(eq(tiptapEditor.state.doc, expectedDoc)).toBe(true);
+ done();
+ }),
+ );
+
+ tiptapEditor.commands.uploadAttachment({ file: imageFile });
+ });
+
+ it('updates the inserted image with canonicalSrc when upload is successful', async () => {
+ const expectedDoc = doc(
+ p(
+ image({
+ canonicalSrc: 'test-file.png',
+ src: base64EncodedFile,
+ alt: 'test-file',
+ uploading: false,
+ }),
+ ),
+ );
+
+ tiptapEditor.commands.uploadAttachment({ file: imageFile });
+
+ await waitForPromises();
+
+ expect(eq(tiptapEditor.state.doc, expectedDoc)).toBe(true);
+ });
+ });
+
+ describe('when uploading request fails', () => {
+ beforeEach(() => {
+ mock.onPost().reply(httpStatus.INTERNAL_SERVER_ERROR);
+ });
+
+ it('resets the doc to orginal state', async () => {
+ const expectedDoc = doc(p(''));
+
+ tiptapEditor.commands.uploadAttachment({ file: imageFile });
+
+ await waitForPromises();
+
+ expect(eq(tiptapEditor.state.doc, expectedDoc)).toBe(true);
+ });
+
+ it('emits an error event that includes an error message', (done) => {
+ tiptapEditor.commands.uploadAttachment({ file: imageFile });
+
+ tiptapEditor.on('error', (message) => {
+ expect(message).toBe('An error occurred while uploading the image. Please try again.');
+ done();
+ });
+ });
+ });
+ });
+
+ describe('when the file has a zip (or any other attachment) mime type', () => {
+ const markdownApiResult = loadMarkdownApiResult('project_wiki_attachment_link').body;
+
+ beforeEach(() => {
+ renderMarkdown.mockResolvedValue(markdownApiResult);
+ });
+
+ describe('when uploading succeeds', () => {
+ const successResponse = {
+ link: {
+ markdown: '[test-file](test-file.zip)',
+ },
+ };
+
+ beforeEach(() => {
+ mock.onPost().reply(httpStatus.OK, successResponse);
+ });
+
+ it('inserts a loading mark', (done) => {
+ const expectedDoc = doc(p(loading({ label: 'test-file' })));
+
+ tiptapEditor.on(
+ 'update',
+ once(() => {
+ expect(eq(tiptapEditor.state.doc, expectedDoc)).toBe(true);
+ done();
+ }),
+ );
+
+ tiptapEditor.commands.uploadAttachment({ file: attachmentFile });
+ });
+
+ it('updates the loading mark with a link with canonicalSrc and href attrs', async () => {
+ const [, group, project] = markdownApiResult.match(/\/(group[0-9]+)\/(project[0-9]+)\//);
+ const expectedDoc = doc(
+ p(
+ link(
+ {
+ canonicalSrc: 'test-file.zip',
+ href: `/${group}/${project}/-/wikis/test-file.zip`,
+ },
+ 'test-file',
+ ),
+ ),
+ );
+
+ tiptapEditor.commands.uploadAttachment({ file: attachmentFile });
+
+ await waitForPromises();
+
+ expect(eq(tiptapEditor.state.doc, expectedDoc)).toBe(true);
+ });
+ });
+
+ describe('when uploading request fails', () => {
+ beforeEach(() => {
+ mock.onPost().reply(httpStatus.INTERNAL_SERVER_ERROR);
+ });
+
+ it('resets the doc to orginal state', async () => {
+ const expectedDoc = doc(p(''));
+
+ tiptapEditor.commands.uploadAttachment({ file: attachmentFile });
+
+ await waitForPromises();
+
+ expect(eq(tiptapEditor.state.doc, expectedDoc)).toBe(true);
+ });
+
+ it('emits an error event that includes an error message', (done) => {
+ tiptapEditor.commands.uploadAttachment({ file: attachmentFile });
+
+ tiptapEditor.on('error', (message) => {
+ expect(message).toBe('An error occurred while uploading the file. Please try again.');
+ done();
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/content_editor/extensions/image_spec.js b/spec/frontend/content_editor/extensions/image_spec.js
deleted file mode 100644
index 09b7274839e..00000000000
--- a/spec/frontend/content_editor/extensions/image_spec.js
+++ /dev/null
@@ -1,193 +0,0 @@
-import axios from 'axios';
-import MockAdapter from 'axios-mock-adapter';
-import { once } from 'lodash';
-import waitForPromises from 'helpers/wait_for_promises';
-import Image from '~/content_editor/extensions/image';
-import httpStatus from '~/lib/utils/http_status';
-import { loadMarkdownApiResult } from '../markdown_processing_examples';
-import { createTestEditor, createDocBuilder } from '../test_utils';
-
-describe('content_editor/extensions/image', () => {
- let tiptapEditor;
- let eq;
- let doc;
- let p;
- let image;
- let renderMarkdown;
- let mock;
- const uploadsPath = '/uploads/';
- const validFile = new File(['foo'], 'foo.png', { type: 'image/png' });
- const invalidFile = new File(['foo'], 'bar.html', { type: 'text/html' });
-
- beforeEach(() => {
- renderMarkdown = jest
- .fn()
- .mockResolvedValue(loadMarkdownApiResult('project_wiki_attachment_image').body);
-
- tiptapEditor = createTestEditor({
- extensions: [Image.configure({ renderMarkdown, uploadsPath })],
- });
-
- ({
- builders: { doc, p, image },
- eq,
- } = createDocBuilder({
- tiptapEditor,
- names: { image: { nodeType: Image.name } },
- }));
-
- mock = new MockAdapter(axios);
- });
-
- afterEach(() => {
- mock.reset();
- });
-
- it.each`
- file | valid | description
- ${validFile} | ${true} | ${'handles paste event when mime type is valid'}
- ${invalidFile} | ${false} | ${'does not handle paste event when mime type is invalid'}
- `('$description', ({ file, valid }) => {
- const pasteEvent = Object.assign(new Event('paste'), {
- clipboardData: {
- files: [file],
- },
- });
- let handled;
-
- tiptapEditor.view.someProp('handlePaste', (eventHandler) => {
- handled = eventHandler(tiptapEditor.view, pasteEvent);
- });
-
- expect(handled).toBe(valid);
- });
-
- it.each`
- file | valid | description
- ${validFile} | ${true} | ${'handles drop event when mime type is valid'}
- ${invalidFile} | ${false} | ${'does not handle drop event when mime type is invalid'}
- `('$description', ({ file, valid }) => {
- const dropEvent = Object.assign(new Event('drop'), {
- dataTransfer: {
- files: [file],
- },
- });
- let handled;
-
- tiptapEditor.view.someProp('handleDrop', (eventHandler) => {
- handled = eventHandler(tiptapEditor.view, dropEvent);
- });
-
- expect(handled).toBe(valid);
- });
-
- it('handles paste event when mime type is correct', () => {
- const pasteEvent = Object.assign(new Event('paste'), {
- clipboardData: {
- files: [new File(['foo'], 'foo.png', { type: 'image/png' })],
- },
- });
- const handled = tiptapEditor.view.someProp('handlePaste', (eventHandler) => {
- return eventHandler(tiptapEditor.view, pasteEvent);
- });
-
- expect(handled).toBe(true);
- });
-
- describe('uploadImage command', () => {
- describe('when file has correct mime type', () => {
- let initialDoc;
- const base64EncodedFile = 'data:image/png;base64,Zm9v';
-
- beforeEach(() => {
- initialDoc = doc(p(''));
- tiptapEditor.commands.setContent(initialDoc.toJSON());
- });
-
- describe('when uploading image succeeds', () => {
- const successResponse = {
- link: {
- markdown: '[image](/uploads/25265/image.png)',
- },
- };
-
- beforeEach(() => {
- mock.onPost().reply(httpStatus.OK, successResponse);
- });
-
- it('inserts an image with src set to the encoded image file and uploading true', (done) => {
- const expectedDoc = doc(p(image({ uploading: true, src: base64EncodedFile })));
-
- tiptapEditor.on(
- 'update',
- once(() => {
- expect(eq(tiptapEditor.state.doc, expectedDoc)).toBe(true);
- done();
- }),
- );
-
- tiptapEditor.commands.uploadImage({ file: validFile });
- });
-
- it('updates the inserted image with canonicalSrc when upload is successful', async () => {
- const expectedDoc = doc(
- p(
- image({
- canonicalSrc: 'test-file.png',
- src: base64EncodedFile,
- alt: 'test file',
- uploading: false,
- }),
- ),
- );
-
- tiptapEditor.commands.uploadImage({ file: validFile });
-
- await waitForPromises();
-
- expect(eq(tiptapEditor.state.doc, expectedDoc)).toBe(true);
- });
- });
-
- describe('when uploading image request fails', () => {
- beforeEach(() => {
- mock.onPost().reply(httpStatus.INTERNAL_SERVER_ERROR);
- });
-
- it('resets the doc to orginal state', async () => {
- const expectedDoc = doc(p(''));
-
- tiptapEditor.commands.uploadImage({ file: validFile });
-
- await waitForPromises();
-
- expect(eq(tiptapEditor.state.doc, expectedDoc)).toBe(true);
- });
-
- it('emits an error event that includes an error message', (done) => {
- tiptapEditor.commands.uploadImage({ file: validFile });
-
- tiptapEditor.on('error', (message) => {
- expect(message).toBe('An error occurred while uploading the image. Please try again.');
- done();
- });
- });
- });
- });
-
- describe('when file does not have correct mime type', () => {
- let initialDoc;
-
- beforeEach(() => {
- initialDoc = doc(p(''));
- tiptapEditor.commands.setContent(initialDoc.toJSON());
- });
-
- it('does not start the upload image process', () => {
- tiptapEditor.commands.uploadImage({ file: invalidFile });
-
- expect(eq(tiptapEditor.state.doc, initialDoc)).toBe(true);
- });
- });
- });
-});
diff --git a/spec/frontend/content_editor/services/create_content_editor_spec.js b/spec/frontend/content_editor/services/create_content_editor_spec.js
index a6d52ddabef..b78e7f0862d 100644
--- a/spec/frontend/content_editor/services/create_content_editor_spec.js
+++ b/spec/frontend/content_editor/services/create_content_editor_spec.js
@@ -52,9 +52,9 @@ describe('content_editor/services/create_editor', () => {
expect(() => createContentEditor()).toThrow(PROVIDE_SERIALIZER_OR_RENDERER_ERROR);
});
- it('provides uploadsPath and renderMarkdown function to Image extension', () => {
+ it('provides uploadsPath and renderMarkdown function to Attachment extension', () => {
expect(
- editor.tiptapEditor.extensionManager.extensions.find((e) => e.name === 'image').options,
+ editor.tiptapEditor.extensionManager.extensions.find((e) => e.name === 'attachment').options,
).toMatchObject({
uploadsPath,
renderMarkdown,
diff --git a/spec/frontend/content_editor/services/upload_file_spec.js b/spec/frontend/content_editor/services/upload_helpers_spec.js
index 87c5298079e..ee9333232db 100644
--- a/spec/frontend/content_editor/services/upload_file_spec.js
+++ b/spec/frontend/content_editor/services/upload_helpers_spec.js
@@ -1,9 +1,9 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
-import { uploadFile } from '~/content_editor/services/upload_file';
+import { uploadFile } from '~/content_editor/services/upload_helpers';
import httpStatus from '~/lib/utils/http_status';
-describe('content_editor/services/upload_file', () => {
+describe('content_editor/services/upload_helpers', () => {
const uploadsPath = '/uploads';
const file = new File(['content'], 'file.txt');
// TODO: Replace with automated fixture
diff --git a/spec/frontend/notes/components/noteable_note_spec.js b/spec/frontend/notes/components/noteable_note_spec.js
index f217dfd2e48..467a8bec21b 100644
--- a/spec/frontend/notes/components/noteable_note_spec.js
+++ b/spec/frontend/notes/components/noteable_note_spec.js
@@ -258,7 +258,11 @@ describe('issue_note', () => {
},
});
- noteBodyComponent.vm.$emit('handleFormUpdate', noteBody, null, () => {});
+ noteBodyComponent.vm.$emit('handleFormUpdate', {
+ noteText: noteBody,
+ parentElement: null,
+ callback: () => {},
+ });
await waitForPromises();
expect(alertSpy).not.toHaveBeenCalled();
@@ -287,14 +291,18 @@ describe('issue_note', () => {
const noteBody = wrapper.findComponent(NoteBody);
noteBody.vm.resetAutoSave = () => {};
- noteBody.vm.$emit('handleFormUpdate', updatedText, null, () => {});
+ noteBody.vm.$emit('handleFormUpdate', {
+ noteText: updatedText,
+ parentElement: null,
+ callback: () => {},
+ });
await wrapper.vm.$nextTick();
let noteBodyProps = noteBody.props();
expect(noteBodyProps.note.note_html).toBe(`<p>${updatedText}</p>\n`);
- noteBody.vm.$emit('cancelForm');
+ noteBody.vm.$emit('cancelForm', {});
await wrapper.vm.$nextTick();
noteBodyProps = noteBody.props();
@@ -305,7 +313,12 @@ describe('issue_note', () => {
describe('formUpdateHandler', () => {
const updateNote = jest.fn();
- const params = ['', null, jest.fn(), ''];
+ const params = {
+ noteText: '',
+ parentElement: null,
+ callback: jest.fn(),
+ resolveDiscussion: false,
+ };
const updateActions = () => {
store.hotUpdate({
@@ -325,14 +338,14 @@ describe('issue_note', () => {
it('responds to handleFormUpdate', () => {
createWrapper();
updateActions();
- wrapper.findComponent(NoteBody).vm.$emit('handleFormUpdate', ...params);
+ wrapper.findComponent(NoteBody).vm.$emit('handleFormUpdate', params);
expect(wrapper.emitted('handleUpdateNote')).toBeTruthy();
});
it('does not stringify empty position', () => {
createWrapper();
updateActions();
- wrapper.findComponent(NoteBody).vm.$emit('handleFormUpdate', ...params);
+ wrapper.findComponent(NoteBody).vm.$emit('handleFormUpdate', params);
expect(updateNote.mock.calls[0][1].note.note.position).toBeUndefined();
});
@@ -341,7 +354,7 @@ describe('issue_note', () => {
const expectation = JSON.stringify(position);
createWrapper({ note: { ...note, position } });
updateActions();
- wrapper.findComponent(NoteBody).vm.$emit('handleFormUpdate', ...params);
+ wrapper.findComponent(NoteBody).vm.$emit('handleFormUpdate', params);
expect(updateNote.mock.calls[0][1].note.note.position).toBe(expectation);
});
});
diff --git a/spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js b/spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js
index b8299d44f13..84863eac3d3 100644
--- a/spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js
+++ b/spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js
@@ -3,6 +3,7 @@ import Vuex from 'vuex';
import CodequalityIssueBody from '~/reports/codequality_report/components/codequality_issue_body.vue';
import GroupedCodequalityReportsApp from '~/reports/codequality_report/grouped_codequality_reports_app.vue';
import { getStoreConfig } from '~/reports/codequality_report/store';
+import { STATUS_NOT_FOUND } from '~/reports/constants';
import { parsedReportIssues } from './mock_data';
const localVue = createLocalVue();
@@ -14,8 +15,6 @@ describe('Grouped code quality reports app', () => {
const PATHS = {
codequalityHelpPath: 'codequality_help.html',
- basePath: 'base.json',
- headPath: 'head.json',
baseBlobPath: 'base/blob/path/',
headBlobPath: 'head/blob/path/',
};
@@ -127,21 +126,6 @@ describe('Grouped code quality reports app', () => {
});
});
- describe('when there is a head report but no base report', () => {
- beforeEach(() => {
- mockStore.state.basePath = null;
- mockStore.state.hasError = true;
- });
-
- it('renders error text', () => {
- expect(findWidget().text()).toContain('Failed to load codeclimate report');
- });
-
- it('renders a help icon with more information', () => {
- expect(findWidget().find('[data-testid="question-icon"]').exists()).toBe(true);
- });
- });
-
describe('on error', () => {
beforeEach(() => {
mockStore.state.hasError = true;
@@ -154,5 +138,15 @@ describe('Grouped code quality reports app', () => {
it('does not render a help icon', () => {
expect(findWidget().find('[data-testid="question-icon"]').exists()).toBe(false);
});
+
+ describe('when base report was not found', () => {
+ beforeEach(() => {
+ mockStore.state.status = STATUS_NOT_FOUND;
+ });
+
+ it('renders a help icon with more information', () => {
+ expect(findWidget().find('[data-testid="question-icon"]').exists()).toBe(true);
+ });
+ });
});
});
diff --git a/spec/frontend/reports/codequality_report/store/actions_spec.js b/spec/frontend/reports/codequality_report/store/actions_spec.js
index 2255b676074..1821390786b 100644
--- a/spec/frontend/reports/codequality_report/store/actions_spec.js
+++ b/spec/frontend/reports/codequality_report/store/actions_spec.js
@@ -5,6 +5,7 @@ import axios from '~/lib/utils/axios_utils';
import createStore from '~/reports/codequality_report/store';
import * as actions from '~/reports/codequality_report/store/actions';
import * as types from '~/reports/codequality_report/store/mutation_types';
+import { STATUS_NOT_FOUND } from '~/reports/constants';
import { reportIssues, parsedReportIssues } from '../mock_data';
const pollInterval = 123;
@@ -24,8 +25,6 @@ describe('Codequality Reports actions', () => {
describe('setPaths', () => {
it('should commit SET_PATHS mutation', (done) => {
const paths = {
- basePath: 'basePath',
- headPath: 'headPath',
baseBlobPath: 'baseBlobPath',
headBlobPath: 'headBlobPath',
reportsPath: 'reportsPath',
@@ -49,7 +48,6 @@ describe('Codequality Reports actions', () => {
beforeEach(() => {
localState.reportsPath = endpoint;
- localState.basePath = '/base/path';
mock = new MockAdapter(axios);
});
@@ -92,16 +90,17 @@ describe('Codequality Reports actions', () => {
});
});
- describe('with no base path', () => {
+ describe('when base report is not found', () => {
it('commits REQUEST_REPORTS and dispatches receiveReportsError', (done) => {
- localState.basePath = null;
+ const data = { status: STATUS_NOT_FOUND };
+ mock.onGet(`${TEST_HOST}/codequality_reports.json`).reply(200, data);
testAction(
actions.fetchReports,
null,
localState,
[{ type: types.REQUEST_REPORTS }],
- [{ type: 'receiveReportsError' }],
+ [{ type: 'receiveReportsError', payload: data }],
done,
);
});
diff --git a/spec/frontend/reports/codequality_report/store/getters_spec.js b/spec/frontend/reports/codequality_report/store/getters_spec.js
index de025f814ef..0378171084d 100644
--- a/spec/frontend/reports/codequality_report/store/getters_spec.js
+++ b/spec/frontend/reports/codequality_report/store/getters_spec.js
@@ -1,6 +1,6 @@
import createStore from '~/reports/codequality_report/store';
import * as getters from '~/reports/codequality_report/store/getters';
-import { LOADING, ERROR, SUCCESS } from '~/reports/constants';
+import { LOADING, ERROR, SUCCESS, STATUS_NOT_FOUND } from '~/reports/constants';
describe('Codequality reports store getters', () => {
let localState;
@@ -76,10 +76,9 @@ describe('Codequality reports store getters', () => {
});
describe('codequalityPopover', () => {
- describe('when head report is available but base report is not', () => {
+ describe('when base report is not available', () => {
it('returns a popover with a documentation link', () => {
- localState.headPath = 'head.json';
- localState.basePath = undefined;
+ localState.status = STATUS_NOT_FOUND;
localState.helpPath = 'codequality_help.html';
expect(getters.codequalityPopover(localState).title).toEqual(
diff --git a/spec/frontend/reports/codequality_report/store/mutations_spec.js b/spec/frontend/reports/codequality_report/store/mutations_spec.js
index 8bc6bb26c2a..6e14cd7438b 100644
--- a/spec/frontend/reports/codequality_report/store/mutations_spec.js
+++ b/spec/frontend/reports/codequality_report/store/mutations_spec.js
@@ -1,5 +1,6 @@
import createStore from '~/reports/codequality_report/store';
import mutations from '~/reports/codequality_report/store/mutations';
+import { STATUS_NOT_FOUND } from '~/reports/constants';
describe('Codequality Reports mutations', () => {
let localState;
@@ -12,24 +13,18 @@ describe('Codequality Reports mutations', () => {
describe('SET_PATHS', () => {
it('sets paths to given values', () => {
- const basePath = 'base.json';
- const headPath = 'head.json';
const baseBlobPath = 'base/blob/path/';
const headBlobPath = 'head/blob/path/';
const reportsPath = 'reports.json';
const helpPath = 'help.html';
mutations.SET_PATHS(localState, {
- basePath,
- headPath,
baseBlobPath,
headBlobPath,
reportsPath,
helpPath,
});
- expect(localState.basePath).toEqual(basePath);
- expect(localState.headPath).toEqual(headPath);
expect(localState.baseBlobPath).toEqual(baseBlobPath);
expect(localState.headBlobPath).toEqual(headBlobPath);
expect(localState.reportsPath).toEqual(reportsPath);
@@ -58,9 +53,10 @@ describe('Codequality Reports mutations', () => {
expect(localState.hasError).toEqual(false);
});
- it('clears statusReason', () => {
+ it('clears status and statusReason', () => {
mutations.RECEIVE_REPORTS_SUCCESS(localState, {});
+ expect(localState.status).toEqual('');
expect(localState.statusReason).toEqual('');
});
@@ -86,6 +82,13 @@ describe('Codequality Reports mutations', () => {
expect(localState.hasError).toEqual(true);
});
+ it('sets status based on error object', () => {
+ const error = { status: STATUS_NOT_FOUND };
+ mutations.RECEIVE_REPORTS_ERROR(localState, error);
+
+ expect(localState.status).toEqual(error.status);
+ });
+
it('sets statusReason to string from error response data', () => {
const data = { status_reason: 'This merge request does not have codequality reports' };
const error = { response: { data } };
diff --git a/spec/frontend/vue_mr_widget/mock_data.js b/spec/frontend/vue_mr_widget/mock_data.js
index e6f1e15d718..15dcbb99623 100644
--- a/spec/frontend/vue_mr_widget/mock_data.js
+++ b/spec/frontend/vue_mr_widget/mock_data.js
@@ -234,14 +234,11 @@ export default {
can_revert_on_current_merge_request: true,
can_cherry_pick_on_current_merge_request: true,
},
- codeclimate: {
- head_path: 'head.json',
- base_path: 'base.json',
- },
blob_path: {
base_path: 'blob_path',
head_path: 'blob_path',
},
+ codequality_reports_path: 'codequality_reports.json',
codequality_help_path: 'code_quality.html',
target_branch_path: '/root/acets-app/branches/main',
source_branch_path: '/root/acets-app/branches/daaaa',
diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb
index 5703bfeaea7..8447b92adbf 100644
--- a/spec/helpers/groups_helper_spec.rb
+++ b/spec/helpers/groups_helper_spec.rb
@@ -263,42 +263,6 @@ RSpec.describe GroupsHelper do
end
end
- describe '#group_container_registry_nav' do
- let_it_be(:group) { create(:group, :public) }
- let_it_be(:user) { create(:user) }
-
- before do
- stub_container_registry_config(enabled: true)
- allow(helper).to receive(:current_user) { user }
- allow(helper).to receive(:can?).with(user, :read_container_image, group) { true }
- helper.instance_variable_set(:@group, group)
- end
-
- subject { helper.group_container_registry_nav? }
-
- context 'when container registry is enabled' do
- it { is_expected.to be_truthy }
-
- it 'is disabled for guest' do
- allow(helper).to receive(:can?).with(user, :read_container_image, group) { false }
- expect(subject).to be false
- end
- end
-
- context 'when container registry is not enabled' do
- before do
- stub_container_registry_config(enabled: false)
- end
-
- it { is_expected.to be_falsy }
-
- it 'is disabled for guests' do
- allow(helper).to receive(:can?).with(user, :read_container_image, group) { false }
- expect(subject).to be false
- end
- end
- end
-
describe '#group_sidebar_links' do
let_it_be(:group) { create(:group, :public) }
let_it_be(:user) { create(:user) }
diff --git a/spec/lib/gitlab/database/connection_spec.rb b/spec/lib/gitlab/database/connection_spec.rb
index 517d40deb1c..52e43fb0f61 100644
--- a/spec/lib/gitlab/database/connection_spec.rb
+++ b/spec/lib/gitlab/database/connection_spec.rb
@@ -378,42 +378,6 @@ RSpec.describe Gitlab::Database::Connection do
end
end
- describe '#create_connection_pool' do
- it 'creates a new connection pool with specific pool size' do
- pool = connection.create_connection_pool(5)
-
- begin
- expect(pool)
- .to be_kind_of(ActiveRecord::ConnectionAdapters::ConnectionPool)
-
- expect(pool.db_config.pool).to eq(5)
- ensure
- pool.disconnect!
- end
- end
-
- it 'allows setting of a custom hostname' do
- pool = connection.create_connection_pool(5, '127.0.0.1')
-
- begin
- expect(pool.db_config.host).to eq('127.0.0.1')
- ensure
- pool.disconnect!
- end
- end
-
- it 'allows setting of a custom hostname and port' do
- pool = connection.create_connection_pool(5, '127.0.0.1', 5432)
-
- begin
- expect(pool.db_config.host).to eq('127.0.0.1')
- expect(pool.db_config.configuration_hash[:port]).to eq(5432)
- ensure
- pool.disconnect!
- end
- end
- end
-
describe '#cached_column_exists?' do
it 'only retrieves data once' do
expect(connection.scope.connection)
diff --git a/spec/lib/gitlab/database/load_balancing/host_list_spec.rb b/spec/lib/gitlab/database/load_balancing/host_list_spec.rb
index 6b88505de1a..6a358b5d430 100644
--- a/spec/lib/gitlab/database/load_balancing/host_list_spec.rb
+++ b/spec/lib/gitlab/database/load_balancing/host_list_spec.rb
@@ -3,25 +3,17 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::LoadBalancing::HostList do
- def expect_metrics(hosts)
- expect(Gitlab::Metrics.registry.get(:db_load_balancing_hosts).get({})).to eq(hosts)
- end
-
- before do
- allow(Gitlab::Database.main)
- .to receive(:create_connection_pool)
- .and_return(ActiveRecord::Base.connection_pool)
- end
-
+ let(:db_host) { ActiveRecord::Base.connection_pool.db_config.host }
let(:load_balancer) { double(:load_balancer) }
let(:host_count) { 2 }
+ let(:hosts) { Array.new(host_count) { Gitlab::Database::LoadBalancing::Host.new(db_host, load_balancer, port: 5432) } }
+ let(:host_list) { described_class.new(hosts) }
- let(:host_list) do
- hosts = Array.new(host_count) do
- Gitlab::Database::LoadBalancing::Host.new('localhost', load_balancer, port: 5432)
+ before do
+ # each call generate a new replica pool
+ allow(load_balancer).to receive(:create_replica_connection_pool) do
+ double(:replica_connection_pool)
end
-
- described_class.new(hosts)
end
describe '#initialize' do
@@ -42,8 +34,8 @@ RSpec.describe Gitlab::Database::LoadBalancing::HostList do
context 'with ports' do
it 'returns the host names of all hosts' do
hosts = [
- ['localhost', 5432],
- ['localhost', 5432]
+ [db_host, 5432],
+ [db_host, 5432]
]
expect(host_list.host_names_and_ports).to eq(hosts)
@@ -51,18 +43,12 @@ RSpec.describe Gitlab::Database::LoadBalancing::HostList do
end
context 'without ports' do
- let(:host_list) do
- hosts = Array.new(2) do
- Gitlab::Database::LoadBalancing::Host.new('localhost', load_balancer)
- end
-
- described_class.new(hosts)
- end
+ let(:hosts) { Array.new(2) { Gitlab::Database::LoadBalancing::Host.new(db_host, load_balancer) } }
it 'returns the host names of all hosts' do
hosts = [
- ['localhost', nil],
- ['localhost', nil]
+ [db_host, nil],
+ [db_host, nil]
]
expect(host_list.host_names_and_ports).to eq(hosts)
@@ -71,10 +57,6 @@ RSpec.describe Gitlab::Database::LoadBalancing::HostList do
end
describe '#manage_pool?' do
- before do
- allow(Gitlab::Database.main).to receive(:create_connection_pool) { double(:connection) }
- end
-
context 'when the testing pool belongs to one host of the host list' do
it 'returns true' do
pool = host_list.hosts.first.pool
@@ -185,4 +167,8 @@ RSpec.describe Gitlab::Database::LoadBalancing::HostList do
end
end
end
+
+ def expect_metrics(hosts)
+ expect(Gitlab::Metrics.registry.get(:db_load_balancing_hosts).get({})).to eq(hosts)
+ end
end
diff --git a/spec/lib/gitlab/database/load_balancing/host_spec.rb b/spec/lib/gitlab/database/load_balancing/host_spec.rb
index 23467e0ae34..f42ac8be1bb 100644
--- a/spec/lib/gitlab/database/load_balancing/host_spec.rb
+++ b/spec/lib/gitlab/database/load_balancing/host_spec.rb
@@ -3,15 +3,16 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::LoadBalancing::Host do
- let(:load_balancer) do
- Gitlab::Database::LoadBalancing::LoadBalancer.new(%w[localhost])
- end
+ let(:load_balancer) { Gitlab::Database::LoadBalancing::LoadBalancer.new }
- let(:host) { load_balancer.host_list.hosts.first }
+ let(:host) do
+ Gitlab::Database::LoadBalancing::Host.new('localhost', load_balancer)
+ end
before do
- allow(Gitlab::Database.main).to receive(:create_connection_pool)
- .and_return(ActiveRecord::Base.connection_pool)
+ allow(load_balancer).to receive(:create_replica_connection_pool) do
+ ActiveRecord::Base.connection_pool
+ end
end
def raise_and_wrap(wrapper, original)
@@ -63,7 +64,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::Host do
expect(host.pool)
.to receive(:disconnect!)
- host.disconnect!(1)
+ host.disconnect!(timeout: 1)
end
end
diff --git a/spec/lib/gitlab/database/load_balancing/load_balancer_spec.rb b/spec/lib/gitlab/database/load_balancing/load_balancer_spec.rb
index 01a17cb2805..358f382bc39 100644
--- a/spec/lib/gitlab/database/load_balancing/load_balancer_spec.rb
+++ b/spec/lib/gitlab/database/load_balancing/load_balancer_spec.rb
@@ -3,21 +3,22 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer, :request_store do
- let(:pool) { Gitlab::Database.main.create_connection_pool(2) }
let(:conflict_error) { Class.new(RuntimeError) }
-
- let(:lb) { described_class.new(%w(localhost localhost)) }
+ let(:db_host) { ActiveRecord::Base.connection_pool.db_config.host }
+ let(:lb) { described_class.new([db_host, db_host]) }
let(:request_cache) { lb.send(:request_cache) }
before do
- allow(Gitlab::Database.main).to receive(:create_connection_pool)
- .and_return(pool)
stub_const(
'Gitlab::Database::LoadBalancing::LoadBalancer::PG::TRSerializationFailure',
conflict_error
)
end
+ after do |example|
+ lb.disconnect!(timeout: 0) unless example.metadata[:skip_disconnect]
+ end
+
def raise_and_wrap(wrapper, original)
raise original
rescue original.class
@@ -239,7 +240,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer, :request_store do
context 'when the connection comes from the primary pool' do
it 'returns :primary' do
connection = double(:connection)
- allow(connection).to receive(:pool).and_return(ActiveRecord::Base.connection_pool)
+ allow(connection).to receive(:pool).and_return(lb.send(:pool))
expect(lb.db_role_for_connection(connection)).to be(:primary)
end
@@ -271,8 +272,8 @@ RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer, :request_store do
end
it 'does not create conflicts with other load balancers when caching hosts' do
- lb1 = described_class.new(%w(localhost localhost), ActiveRecord::Base)
- lb2 = described_class.new(%w(localhost localhost), Ci::CiDatabaseRecord)
+ lb1 = described_class.new([db_host, db_host], ActiveRecord::Base)
+ lb2 = described_class.new([db_host, db_host], Ci::CiDatabaseRecord)
host1 = lb1.host
host2 = lb2.host
@@ -456,4 +457,45 @@ RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer, :request_store do
end
end
end
+
+ describe '#create_replica_connection_pool' do
+ it 'creates a new connection pool with specific pool size and name' do
+ with_replica_pool(5, 'other_host') do |replica_pool|
+ expect(replica_pool)
+ .to be_kind_of(ActiveRecord::ConnectionAdapters::ConnectionPool)
+
+ expect(replica_pool.db_config.host).to eq('other_host')
+ expect(replica_pool.db_config.pool).to eq(5)
+ expect(replica_pool.db_config.name).to end_with("_replica")
+ end
+ end
+
+ it 'allows setting of a custom hostname and port' do
+ with_replica_pool(5, 'other_host', 5432) do |replica_pool|
+ expect(replica_pool.db_config.host).to eq('other_host')
+ expect(replica_pool.db_config.configuration_hash[:port]).to eq(5432)
+ end
+ end
+
+ it 'does not modify connection class pool' do
+ expect { with_replica_pool(5) { } }.not_to change { ActiveRecord::Base.connection_pool }
+ end
+
+ def with_replica_pool(*args)
+ pool = lb.create_replica_connection_pool(*args)
+ yield pool
+ ensure
+ pool&.disconnect!
+ end
+ end
+
+ describe '#disconnect!' do
+ it 'calls disconnect on all hosts with a timeout', :skip_disconnect do
+ expect_next_instances_of(Gitlab::Database::LoadBalancing::Host, 2) do |host|
+ expect(host).to receive(:disconnect!).with(timeout: 30)
+ end
+
+ lb.disconnect!(timeout: 30)
+ end
+ end
end
diff --git a/spec/lib/gitlab/database/load_balancing/service_discovery_spec.rb b/spec/lib/gitlab/database/load_balancing/service_discovery_spec.rb
index b65b68c463e..c853e827144 100644
--- a/spec/lib/gitlab/database/load_balancing/service_discovery_spec.rb
+++ b/spec/lib/gitlab/database/load_balancing/service_discovery_spec.rb
@@ -169,7 +169,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::ServiceDiscovery do
expect(host)
.to receive(:disconnect!)
- .with(2)
+ .with(timeout: 2)
service.replace_hosts([address_bar])
end
diff --git a/spec/lib/gitlab/database/load_balancing_spec.rb b/spec/lib/gitlab/database/load_balancing_spec.rb
index ba5aae110ca..fb482061d7c 100644
--- a/spec/lib/gitlab/database/load_balancing_spec.rb
+++ b/spec/lib/gitlab/database/load_balancing_spec.rb
@@ -3,10 +3,6 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::LoadBalancing do
- before do
- stub_env('ENABLE_LOAD_BALANCING_FOR_FOSS', 'true')
- end
-
describe '.proxy' do
before do
@previous_proxy = ActiveRecord::Base.load_balancing_proxy
diff --git a/spec/lib/gitlab/usage/metric_definition_spec.rb b/spec/lib/gitlab/usage/metric_definition_spec.rb
index f3c3e5fc550..a7cff80e43a 100644
--- a/spec/lib/gitlab/usage/metric_definition_spec.rb
+++ b/spec/lib/gitlab/usage/metric_definition_spec.rb
@@ -87,14 +87,14 @@ RSpec.describe Gitlab::Usage::MetricDefinition do
end
it 'raise exception' do
- expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).at_least(:once).with(instance_of(Gitlab::Usage::Metric::InvalidMetricError))
+ expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).at_least(:once).with(instance_of(Gitlab::Usage::MetricDefinition::InvalidError))
described_class.new(path, attributes).validate!
end
context 'with skip_validation' do
it 'raise exception if skip_validation: false' do
- expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).at_least(:once).with(instance_of(Gitlab::Usage::Metric::InvalidMetricError))
+ expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).at_least(:once).with(instance_of(Gitlab::Usage::MetricDefinition::InvalidError))
described_class.new(path, attributes.merge( { skip_validation: false } )).validate!
end
@@ -113,7 +113,7 @@ RSpec.describe Gitlab::Usage::MetricDefinition do
attributes[:status] = 'broken'
attributes.delete(:repair_issue_url)
- expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).at_least(:once).with(instance_of(Gitlab::Usage::Metric::InvalidMetricError))
+ expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).at_least(:once).with(instance_of(Gitlab::Usage::MetricDefinition::InvalidError))
described_class.new(path, attributes).validate!
end
@@ -173,7 +173,7 @@ RSpec.describe Gitlab::Usage::MetricDefinition do
write_metric(metric1, path, yaml_content)
write_metric(metric2, path, yaml_content)
- expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).with(instance_of(Gitlab::Usage::Metric::InvalidMetricError))
+ expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).with(instance_of(Gitlab::Usage::MetricDefinition::InvalidError))
subject
end
diff --git a/spec/lib/gitlab/usage/metric_spec.rb b/spec/lib/gitlab/usage/metric_spec.rb
index d4a789419a4..d83f59e4a7d 100644
--- a/spec/lib/gitlab/usage/metric_spec.rb
+++ b/spec/lib/gitlab/usage/metric_spec.rb
@@ -3,27 +3,46 @@
require 'spec_helper'
RSpec.describe Gitlab::Usage::Metric do
- describe '#definition' do
- it 'returns key_path metric definiton' do
- expect(described_class.new(key_path: 'uuid').definition).to be_an(Gitlab::Usage::MetricDefinition)
- end
+ let!(:issue) { create(:issue) }
+
+ let(:attributes) do
+ {
+ data_category: "Operational",
+ key_path: "counts.issues",
+ description: "Count of Issues created",
+ product_section: "dev",
+ product_stage: "plan",
+ product_group: "group::plan",
+ product_category: "issue_tracking",
+ value_type: "number",
+ status: "data_available",
+ time_frame: "all",
+ data_source: "database",
+ instrumentation_class: "CountIssuesMetric",
+ distribution: %w(ce ee),
+ tier: %w(free premium ultimate)
+ }
end
- describe '#unflatten_default_path' do
- using RSpec::Parameterized::TableSyntax
+ let(:issue_count_metric_definiton) do
+ double(:issue_count_metric_definiton,
+ attributes.merge({ attributes: attributes })
+ )
+ end
- where(:key_path, :value, :expected_hash) do
- 'uuid' | nil | { uuid: nil }
- 'uuid' | '1111' | { uuid: '1111' }
- 'counts.issues' | nil | { counts: { issues: nil } }
- 'counts.issues' | 100 | { counts: { issues: 100 } }
- 'usage_activity_by_stage.verify.ci_builds' | 100 | { usage_activity_by_stage: { verify: { ci_builds: 100 } } }
- end
+ before do
+ allow(ApplicationRecord.connection).to receive(:transaction_open?).and_return(false)
+ end
- with_them do
- subject { described_class.new(key_path: key_path, value: value).unflatten_key_path }
+ describe '#with_value' do
+ it 'returns key_path metric with the corresponding value' do
+ expect(described_class.new(issue_count_metric_definiton).with_value).to eq({ counts: { issues: 1 } })
+ end
+ end
- it { is_expected.to eq(expected_hash) }
+ describe '#with_instrumentation' do
+ it 'returns key_path metric with the corresponding generated query' do
+ expect(described_class.new(issue_count_metric_definiton).with_instrumentation).to eq({ counts: { issues: "SELECT COUNT(\"issues\".\"id\") FROM \"issues\"" } })
end
end
end
diff --git a/spec/lib/sidebars/groups/menus/packages_registries_menu_spec.rb b/spec/lib/sidebars/groups/menus/packages_registries_menu_spec.rb
new file mode 100644
index 00000000000..5ebd67462f8
--- /dev/null
+++ b/spec/lib/sidebars/groups/menus/packages_registries_menu_spec.rb
@@ -0,0 +1,163 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Sidebars::Groups::Menus::PackagesRegistriesMenu do
+ let_it_be(:owner) { create(:user) }
+ let_it_be(:group) do
+ build(:group, :private).tap do |g|
+ g.add_owner(owner)
+ end
+ end
+
+ let(:user) { owner }
+ let(:context) { Sidebars::Groups::Context.new(current_user: user, container: group) }
+ let(:menu) { described_class.new(context) }
+
+ describe '#render?' do
+ context 'when menu has menu items to show' do
+ it 'returns true' do
+ expect(menu.render?).to eq true
+ end
+ end
+
+ context 'when menu does not have any menu item to show' do
+ it 'returns false' do
+ stub_container_registry_config(enabled: false)
+ stub_config(packages: { enabled: false })
+ stub_config(dependency_proxy: { enabled: false })
+
+ expect(menu.render?).to eq false
+ end
+ end
+ end
+
+ describe '#link' do
+ let(:registry_enabled) { true }
+ let(:packages_enabled) { true }
+
+ before do
+ stub_container_registry_config(enabled: registry_enabled)
+ stub_config(packages: { enabled: packages_enabled })
+ stub_config(dependency_proxy: { enabled: true })
+ end
+
+ subject { menu.link }
+
+ context 'when Packages Registry is visible' do
+ it 'menu link points to Packages Registry page' do
+ expect(subject).to eq find_menu(menu, :packages_registry).link
+ end
+ end
+
+ context 'when Packages Registry is not visible' do
+ let(:packages_enabled) { false }
+
+ it 'menu link points to Container Registry page' do
+ expect(subject).to eq find_menu(menu, :container_registry).link
+ end
+
+ context 'when Container Registry is not visible' do
+ let(:registry_enabled) { false }
+
+ it 'menu link points to Dependency Proxy page' do
+ expect(subject).to eq find_menu(menu, :dependency_proxy).link
+ end
+ end
+ end
+ end
+
+ describe 'Menu items' do
+ subject { find_menu(menu, item_id) }
+
+ describe 'Packages Registry' do
+ let(:item_id) { :packages_registry }
+
+ context 'when user can read packages' do
+ before do
+ stub_config(packages: { enabled: packages_enabled })
+ end
+
+ context 'when config package setting is disabled' do
+ let(:packages_enabled) { false }
+
+ it 'the menu item is not added to list of menu items' do
+ is_expected.to be_nil
+ end
+ end
+
+ context 'when config package setting is enabled' do
+ let(:packages_enabled) { true }
+
+ it 'the menu item is added to list of menu items' do
+ is_expected.not_to be_nil
+ end
+ end
+ end
+ end
+
+ describe 'Container Registry' do
+ let(:item_id) { :container_registry }
+
+ context 'when user can read container images' do
+ before do
+ stub_container_registry_config(enabled: container_enabled)
+ end
+
+ context 'when config registry setting is disabled' do
+ let(:container_enabled) { false }
+
+ it 'the menu item is not added to list of menu items' do
+ is_expected.to be_nil
+ end
+ end
+
+ context 'when config registry setting is enabled' do
+ let(:container_enabled) { true }
+
+ it 'the menu item is added to list of menu items' do
+ is_expected.not_to be_nil
+ end
+
+ context 'when user cannot read container images' do
+ let(:user) { nil }
+
+ it 'the menu item is not added to list of menu items' do
+ is_expected.to be_nil
+ end
+ end
+ end
+ end
+ end
+
+ describe 'Dependency Proxy' do
+ let(:item_id) { :dependency_proxy }
+
+ before do
+ stub_config(dependency_proxy: { enabled: dependency_enabled })
+ end
+
+ context 'when config dependency_proxy is enabled' do
+ let(:dependency_enabled) { true }
+
+ it 'the menu item is added to list of menu items' do
+ is_expected.not_to be_nil
+ end
+ end
+
+ context 'when config dependency_proxy is not enabled' do
+ let(:dependency_enabled) { false }
+
+ it 'the menu item is not added to list of menu items' do
+ is_expected.to be_nil
+ end
+ end
+ end
+ end
+
+ private
+
+ def find_menu(menu, item)
+ menu.renderable_items.find { |i| i.item_id == item }
+ end
+end
diff --git a/spec/requests/api/graphql/ci/runner_spec.rb b/spec/requests/api/graphql/ci/runner_spec.rb
index cdd46ca4ecc..74547196445 100644
--- a/spec/requests/api/graphql/ci/runner_spec.rb
+++ b/spec/requests/api/graphql/ci/runner_spec.rb
@@ -52,14 +52,14 @@ RSpec.describe 'Query.runner(id)' do
'version' => runner.version,
'shortSha' => runner.short_sha,
'revision' => runner.revision,
- 'locked' => runner.locked,
+ 'locked' => false,
'active' => runner.active,
'status' => runner.status.to_s.upcase,
'maximumTimeout' => runner.maximum_timeout,
'accessLevel' => runner.access_level.to_s.upcase,
'runUntagged' => runner.run_untagged,
'ipAddress' => runner.ip_address,
- 'runnerType' => 'INSTANCE_TYPE',
+ 'runnerType' => runner.instance_type? ? 'INSTANCE_TYPE' : 'PROJECT_TYPE',
'jobCount' => 0,
'projectCount' => nil
)
@@ -109,6 +109,40 @@ RSpec.describe 'Query.runner(id)' do
end
end
+ describe 'for project runner' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(is_locked: [true, false])
+
+ with_them do
+ let(:project_runner) do
+ create(:ci_runner, :project, description: 'Runner 3', contacted_at: 1.day.ago, active: false, locked: is_locked,
+ version: 'adfe157', revision: 'b', ip_address: '10.10.10.10', access_level: 1, run_untagged: true)
+ end
+
+ let(:query) do
+ wrap_fields(query_graphql_path(query_path, all_graphql_fields_for('CiRunner')))
+ end
+
+ let(:query_path) do
+ [
+ [:runner, { id: project_runner.to_global_id.to_s }]
+ ]
+ end
+
+ it 'retrieves correct locked value' do
+ post_graphql(query, current_user: user)
+
+ runner_data = graphql_data_at(:runner)
+
+ expect(runner_data).to match a_hash_including(
+ 'id' => "gid://gitlab/Ci::Runner/#{project_runner.id}",
+ 'locked' => is_locked
+ )
+ end
+ end
+ end
+
describe 'for inactive runner' do
it_behaves_like 'runner details fetch', :inactive_instance_runner
end
diff --git a/spec/requests/projects/merge_requests/diffs_spec.rb b/spec/requests/projects/merge_requests/diffs_spec.rb
index 4d3c14eceea..349cbf1b76c 100644
--- a/spec/requests/projects/merge_requests/diffs_spec.rb
+++ b/spec/requests/projects/merge_requests/diffs_spec.rb
@@ -76,6 +76,78 @@ RSpec.describe 'Merge Requests Diffs' do
subject
end
+ context 'with the different user' do
+ let(:another_user) { create(:user) }
+
+ before do
+ project.add_maintainer(another_user)
+ sign_in(another_user)
+ end
+
+ it_behaves_like 'serializes diffs with expected arguments' do
+ let(:collection) { Gitlab::Diff::FileCollection::MergeRequestDiffBatch }
+ let(:expected_options) { collection_arguments(total_pages: 20) }
+ end
+ end
+
+ context 'with a new unfoldable diff position' do
+ let(:unfoldable_position) do
+ create(:diff_position)
+ end
+
+ before do
+ expect_next_instance_of(Gitlab::Diff::PositionCollection) do |instance|
+ expect(instance)
+ .to receive(:unfoldable)
+ .and_return([unfoldable_position])
+ end
+ end
+
+ it_behaves_like 'serializes diffs with expected arguments' do
+ let(:collection) { Gitlab::Diff::FileCollection::MergeRequestDiffBatch }
+ let(:expected_options) { collection_arguments(total_pages: 20) }
+ end
+ end
+
+ context 'with a new environment' do
+ let(:environment) do
+ create(:environment, :available, project: project)
+ end
+
+ let!(:deployment) do
+ create(:deployment, :success, environment: environment, ref: merge_request.source_branch)
+ end
+
+ it_behaves_like 'serializes diffs with expected arguments' do
+ let(:collection) { Gitlab::Diff::FileCollection::MergeRequestDiffBatch }
+ let(:expected_options) { collection_arguments(total_pages: 20).merge(environment: environment) }
+ end
+ end
+
+ context 'with disabled display_merge_conflicts_in_diff feature' do
+ before do
+ stub_feature_flags(display_merge_conflicts_in_diff: false)
+ end
+
+ it_behaves_like 'serializes diffs with expected arguments' do
+ let(:collection) { Gitlab::Diff::FileCollection::MergeRequestDiffBatch }
+ let(:expected_options) { collection_arguments(total_pages: 20).merge(allow_tree_conflicts: false) }
+ end
+ end
+
+ context 'with diff_head option' do
+ subject { go(page: 0, per_page: 5, diff_head: true) }
+
+ before do
+ merge_request.create_merge_head_diff!
+ end
+
+ it_behaves_like 'serializes diffs with expected arguments' do
+ let(:collection) { Gitlab::Diff::FileCollection::MergeRequestDiffBatch }
+ let(:expected_options) { collection_arguments(total_pages: 20).merge(merge_ref_head_diff: true) }
+ end
+ end
+
context 'with the different pagination option' do
subject { go(page: 5, per_page: 5) }
diff --git a/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb
index f4e681b70ff..7ea9cab5453 100644
--- a/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb
@@ -108,4 +108,30 @@ RSpec.describe 'layouts/nav/sidebar/_group' do
expect(rendered).to have_link('Kubernetes', href: group_clusters_path(group))
end
end
+
+ describe 'Packages & Registries' do
+ it 'has a link to the package registry page' do
+ stub_config(packages: { enabled: true })
+
+ render
+
+ expect(rendered).to have_link('Package Registry', href: group_packages_path(group))
+ end
+
+ it 'has a link to the container registry page' do
+ stub_container_registry_config(enabled: true)
+
+ render
+
+ expect(rendered).to have_link('Container Registry', href: group_container_registries_path(group))
+ end
+
+ it 'has a link to the dependency proxy page' do
+ stub_config(dependency_proxy: { enabled: true })
+
+ render
+
+ expect(rendered).to have_link('Dependency Proxy', href: group_dependency_proxy_path(group))
+ end
+ end
end