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-12-06 18:14:39 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-12-06 18:14:39 +0300
commit55242833f832095a6fcff00b1ccacbc5900ee52a (patch)
tree6e17b16638e60099533473b540fe8f635d2f25da /spec
parent7c31b0312ba0eae4e4ebe54125b13aa2ae5f5db4 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/invites_controller_spec.rb26
-rw-r--r--spec/controllers/registrations_controller_spec.rb34
-rw-r--r--spec/features/invites_spec.rb14
-rw-r--r--spec/features/users/one_trust_csp_spec.rb17
-rw-r--r--spec/frontend/editor/source_editor_markdown_ext_spec.js363
-rw-r--r--spec/frontend/editor/source_editor_markdown_livepreview_ext_spec.js398
-rw-r--r--spec/helpers/notify_helper_spec.rb22
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb97
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb94
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/ensure_resource_groups_spec.rb85
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build_spec.rb28
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb14
-rw-r--r--spec/mailers/notify_spec.rb21
-rw-r--r--spec/models/user_spec.rb14
14 files changed, 732 insertions, 495 deletions
diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb
index d4091461062..dc1fb0454df 100644
--- a/spec/controllers/invites_controller_spec.rb
+++ b/spec/controllers/invites_controller_spec.rb
@@ -120,29 +120,6 @@ RSpec.describe InvitesController do
end
end
- context 'when it is part of the invite_email_from experiment' do
- let(:extra_params) { { invite_type: 'initial_email', experiment_name: 'invite_email_from' } }
-
- it 'tracks the initial join click from email' do
- experiment = double(track: true)
- allow(controller).to receive(:experiment).with(:invite_email_from, actor: member).and_return(experiment)
-
- request
-
- expect(experiment).to have_received(:track).with(:join_clicked)
- end
-
- context 'when member does not exist' do
- let(:raw_invite_token) { '_bogus_token_' }
-
- it 'does not track the experiment' do
- expect(controller).not_to receive(:experiment).with(:invite_email_from, actor: member)
-
- request
- end
- end
- end
-
context 'when member does not exist' do
let(:raw_invite_token) { '_bogus_token_' }
@@ -170,9 +147,8 @@ RSpec.describe InvitesController do
end
context 'when it is not part of our invite email experiment' do
- it 'does not track via experiment', :aggregate_failures do
+ it 'does not track via experiment' do
expect(controller).not_to receive(:experiment).with(:invite_email_preview_text, actor: member)
- expect(controller).not_to receive(:experiment).with(:invite_email_from, actor: member)
request
end
diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb
index baf500c2b57..9094d235366 100644
--- a/spec/controllers/registrations_controller_spec.rb
+++ b/spec/controllers/registrations_controller_spec.rb
@@ -227,40 +227,6 @@ RSpec.describe RegistrationsController do
end
end
end
-
- context 'with the invite_email_preview_text experiment', :experiment do
- let(:extra_session_params) { { invite_email_experiment_name: 'invite_email_from' } }
-
- context 'when member and invite_email_experiment_name exists from the session key value' do
- it 'tracks the invite acceptance' do
- expect(experiment(:invite_email_from)).to track(:accepted)
- .with_context(actor: member)
- .on_next_instance
-
- subject
- end
- end
-
- context 'when member does not exist from the session key value' do
- let(:originating_member_id) { -1 }
-
- it 'does not track invite acceptance' do
- expect(experiment(:invite_email_from)).not_to track(:accepted)
-
- subject
- end
- end
-
- context 'when invite_email_experiment_name does not exist from the session key value' do
- let(:extra_session_params) { {} }
-
- it 'does not track invite acceptance' do
- expect(experiment(:invite_email_from)).not_to track(:accepted)
-
- subject
- end
- end
- end
end
context 'when invite email matches email used on registration' do
diff --git a/spec/features/invites_spec.rb b/spec/features/invites_spec.rb
index f9ab780d2d6..d2bf35166ac 100644
--- a/spec/features/invites_spec.rb
+++ b/spec/features/invites_spec.rb
@@ -240,20 +240,6 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do
end
end
- context 'with invite email acceptance for the invite_email_from experiment', :experiment do
- let(:extra_params) do
- { invite_type: Emails::Members::INITIAL_INVITE, experiment_name: 'invite_email_from' }
- end
-
- it 'tracks the accepted invite' do
- expect(experiment(:invite_email_from)).to track(:accepted)
- .with_context(actor: group_invite)
- .on_next_instance
-
- fill_in_sign_up_form(new_user)
- end
- end
-
it 'signs up and redirects to the group activity page with all the project/groups invitation automatically accepted' do
fill_in_sign_up_form(new_user)
fill_in_welcome_form
diff --git a/spec/features/users/one_trust_csp_spec.rb b/spec/features/users/one_trust_csp_spec.rb
new file mode 100644
index 00000000000..382a0b4be6c
--- /dev/null
+++ b/spec/features/users/one_trust_csp_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'OneTrust content security policy' do
+ let(:user) { create(:user) }
+
+ before do
+ stub_config(extra: { one_trust_id: SecureRandom.uuid })
+ end
+
+ it 'has proper Content Security Policy headers' do
+ visit root_path
+
+ expect(response_headers['Content-Security-Policy']).to include('https://cdn.cookielaw.org https://*.onetrust.com')
+ end
+end
diff --git a/spec/frontend/editor/source_editor_markdown_ext_spec.js b/spec/frontend/editor/source_editor_markdown_ext_spec.js
index 4a53f870f6d..4a50d801296 100644
--- a/spec/frontend/editor/source_editor_markdown_ext_spec.js
+++ b/spec/frontend/editor/source_editor_markdown_ext_spec.js
@@ -1,36 +1,20 @@
import MockAdapter from 'axios-mock-adapter';
-import { Range, Position, editor as monacoEditor } from 'monaco-editor';
-import waitForPromises from 'helpers/wait_for_promises';
-import {
- EXTENSION_MARKDOWN_PREVIEW_PANEL_CLASS,
- EXTENSION_MARKDOWN_PREVIEW_ACTION_ID,
- EXTENSION_MARKDOWN_PREVIEW_PANEL_WIDTH,
- EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS,
- EXTENSION_MARKDOWN_PREVIEW_UPDATE_DELAY,
-} from '~/editor/constants';
+import { Range, Position } from 'monaco-editor';
import { EditorMarkdownExtension } from '~/editor/extensions/source_editor_markdown_ext';
import SourceEditor from '~/editor/source_editor';
-import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
-import syntaxHighlight from '~/syntax_highlight';
-
-jest.mock('~/syntax_highlight');
-jest.mock('~/flash');
describe('Markdown Extension for Source Editor', () => {
let editor;
let instance;
let editorEl;
- let panelSpy;
let mockAxios;
const previewMarkdownPath = '/gitlab/fooGroup/barProj/preview_markdown';
const firstLine = 'This is a';
const secondLine = 'multiline';
const thirdLine = 'string with some **markup**';
const text = `${firstLine}\n${secondLine}\n${thirdLine}`;
- const plaintextPath = 'foo.txt';
const markdownPath = 'foo.md';
- const responseData = '<div>FooBar</div>';
const setSelection = (startLineNumber = 1, startColumn = 1, endLineNumber = 1, endColumn = 1) => {
const selection = new Range(startLineNumber, startColumn, endLineNumber, endColumn);
@@ -42,11 +26,6 @@ describe('Markdown Extension for Source Editor', () => {
const selectionToString = () => instance.getSelection().toString();
const positionToString = () => instance.getPosition().toString();
- const togglePreview = async () => {
- instance.togglePreview();
- await waitForPromises();
- };
-
beforeEach(() => {
mockAxios = new MockAdapter(axios);
setFixtures('<div id="editor" data-editor-loading></div>');
@@ -58,7 +37,6 @@ describe('Markdown Extension for Source Editor', () => {
blobContent: text,
});
instance.use(new EditorMarkdownExtension({ instance, previewMarkdownPath }));
- panelSpy = jest.spyOn(EditorMarkdownExtension, 'togglePreviewPanel');
});
afterEach(() => {
@@ -67,345 +45,6 @@ describe('Markdown Extension for Source Editor', () => {
mockAxios.restore();
});
- it('sets up the instance', () => {
- expect(instance.preview).toEqual({
- el: undefined,
- action: expect.any(Object),
- shown: false,
- modelChangeListener: undefined,
- });
- expect(instance.previewMarkdownPath).toBe(previewMarkdownPath);
- });
-
- describe('model language changes listener', () => {
- let cleanupSpy;
- let actionSpy;
-
- beforeEach(async () => {
- cleanupSpy = jest.spyOn(instance, 'cleanup');
- actionSpy = jest.spyOn(instance, 'setupPreviewAction');
- await togglePreview();
- });
-
- it('cleans up when switching away from markdown', () => {
- expect(instance.cleanup).not.toHaveBeenCalled();
- expect(instance.setupPreviewAction).not.toHaveBeenCalled();
-
- instance.updateModelLanguage(plaintextPath);
-
- expect(cleanupSpy).toHaveBeenCalled();
- expect(actionSpy).not.toHaveBeenCalled();
- });
-
- it.each`
- oldLanguage | newLanguage | setupCalledTimes
- ${'plaintext'} | ${'markdown'} | ${1}
- ${'markdown'} | ${'markdown'} | ${0}
- ${'markdown'} | ${'plaintext'} | ${0}
- ${'markdown'} | ${undefined} | ${0}
- ${undefined} | ${'markdown'} | ${1}
- `(
- 'correctly handles re-enabling of the action when switching from $oldLanguage to $newLanguage',
- ({ oldLanguage, newLanguage, setupCalledTimes } = {}) => {
- expect(actionSpy).not.toHaveBeenCalled();
- instance.updateModelLanguage(oldLanguage);
- instance.updateModelLanguage(newLanguage);
- expect(actionSpy).toHaveBeenCalledTimes(setupCalledTimes);
- },
- );
- });
-
- describe('model change listener', () => {
- let cleanupSpy;
- let actionSpy;
-
- beforeEach(() => {
- cleanupSpy = jest.spyOn(instance, 'cleanup');
- actionSpy = jest.spyOn(instance, 'setupPreviewAction');
- instance.togglePreview();
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- it('does not do anything if there is no model', () => {
- instance.setModel(null);
-
- expect(cleanupSpy).not.toHaveBeenCalled();
- expect(actionSpy).not.toHaveBeenCalled();
- });
-
- it('cleans up the preview when the model changes', () => {
- instance.setModel(monacoEditor.createModel('foo'));
- expect(cleanupSpy).toHaveBeenCalled();
- });
-
- it.each`
- language | setupCalledTimes
- ${'markdown'} | ${1}
- ${'plaintext'} | ${0}
- ${undefined} | ${0}
- `(
- 'correctly handles actions when the new model is $language',
- ({ language, setupCalledTimes } = {}) => {
- instance.setModel(monacoEditor.createModel('foo', language));
-
- expect(actionSpy).toHaveBeenCalledTimes(setupCalledTimes);
- },
- );
- });
-
- describe('cleanup', () => {
- beforeEach(async () => {
- mockAxios.onPost().reply(200, { body: responseData });
- await togglePreview();
- });
-
- it('disposes the modelChange listener and does not fetch preview on content changes', () => {
- expect(instance.preview.modelChangeListener).toBeDefined();
- jest.spyOn(instance, 'fetchPreview');
-
- instance.cleanup();
- instance.setValue('Foo Bar');
- jest.advanceTimersByTime(EXTENSION_MARKDOWN_PREVIEW_UPDATE_DELAY);
-
- expect(instance.fetchPreview).not.toHaveBeenCalled();
- });
-
- it('removes the contextual menu action', () => {
- expect(instance.getAction(EXTENSION_MARKDOWN_PREVIEW_ACTION_ID)).toBeDefined();
-
- instance.cleanup();
-
- expect(instance.getAction(EXTENSION_MARKDOWN_PREVIEW_ACTION_ID)).toBe(null);
- });
-
- it('toggles the `shown` flag', () => {
- expect(instance.preview.shown).toBe(true);
- instance.cleanup();
- expect(instance.preview.shown).toBe(false);
- });
-
- it('toggles the panel only if the preview is visible', () => {
- const { el: previewEl } = instance.preview;
- const parentEl = previewEl.parentElement;
-
- expect(previewEl).toBeVisible();
- expect(parentEl.classList.contains(EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS)).toBe(true);
-
- instance.cleanup();
- expect(previewEl).toBeHidden();
- expect(parentEl.classList.contains(EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS)).toBe(
- false,
- );
-
- instance.cleanup();
- expect(previewEl).toBeHidden();
- expect(parentEl.classList.contains(EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS)).toBe(
- false,
- );
- });
-
- it('toggles the layout only if the preview is visible', () => {
- const { width } = instance.getLayoutInfo();
-
- expect(instance.preview.shown).toBe(true);
-
- instance.cleanup();
-
- const { width: newWidth } = instance.getLayoutInfo();
- expect(newWidth === width / EXTENSION_MARKDOWN_PREVIEW_PANEL_WIDTH).toBe(true);
-
- instance.cleanup();
- expect(newWidth === width / EXTENSION_MARKDOWN_PREVIEW_PANEL_WIDTH).toBe(true);
- });
- });
-
- describe('fetchPreview', () => {
- const fetchPreview = async () => {
- instance.fetchPreview();
- await waitForPromises();
- };
-
- let previewMarkdownSpy;
-
- beforeEach(() => {
- previewMarkdownSpy = jest.fn().mockImplementation(() => [200, { body: responseData }]);
- mockAxios.onPost(previewMarkdownPath).replyOnce((req) => previewMarkdownSpy(req));
- });
-
- it('correctly fetches preview based on previewMarkdownPath', async () => {
- await fetchPreview();
-
- expect(previewMarkdownSpy).toHaveBeenCalledWith(
- expect.objectContaining({ data: JSON.stringify({ text }) }),
- );
- });
-
- it('puts the fetched content into the preview DOM element', async () => {
- instance.preview.el = editorEl.parentElement;
- await fetchPreview();
- expect(instance.preview.el.innerHTML).toEqual(responseData);
- });
-
- it('applies syntax highlighting to the preview content', async () => {
- instance.preview.el = editorEl.parentElement;
- await fetchPreview();
- expect(syntaxHighlight).toHaveBeenCalled();
- });
-
- it('catches the errors when fetching the preview', async () => {
- mockAxios.onPost().reply(500);
-
- await fetchPreview();
- expect(createFlash).toHaveBeenCalled();
- });
- });
-
- describe('setupPreviewAction', () => {
- it('adds the contextual menu action', () => {
- expect(instance.getAction(EXTENSION_MARKDOWN_PREVIEW_ACTION_ID)).toBeDefined();
- });
-
- it('does not set up action if one already exists', () => {
- jest.spyOn(instance, 'addAction').mockImplementation();
-
- instance.setupPreviewAction();
- expect(instance.addAction).not.toHaveBeenCalled();
- });
-
- it('toggles preview when the action is triggered', () => {
- jest.spyOn(instance, 'togglePreview').mockImplementation();
-
- expect(instance.togglePreview).not.toHaveBeenCalled();
-
- const action = instance.getAction(EXTENSION_MARKDOWN_PREVIEW_ACTION_ID);
- action.run();
-
- expect(instance.togglePreview).toHaveBeenCalled();
- });
- });
-
- describe('togglePreview', () => {
- beforeEach(() => {
- mockAxios.onPost().reply(200, { body: responseData });
- });
-
- it('toggles preview flag on instance', () => {
- expect(instance.preview.shown).toBe(false);
-
- instance.togglePreview();
- expect(instance.preview.shown).toBe(true);
-
- instance.togglePreview();
- expect(instance.preview.shown).toBe(false);
- });
-
- describe('panel DOM element set up', () => {
- it('sets up an element to contain the preview and stores it on instance', () => {
- expect(instance.preview.el).toBeUndefined();
-
- instance.togglePreview();
-
- expect(instance.preview.el).toBeDefined();
- expect(instance.preview.el.classList.contains(EXTENSION_MARKDOWN_PREVIEW_PANEL_CLASS)).toBe(
- true,
- );
- });
-
- it('re-uses existing preview DOM element on repeated calls', () => {
- instance.togglePreview();
- const origPreviewEl = instance.preview.el;
- instance.togglePreview();
-
- expect(instance.preview.el).toBe(origPreviewEl);
- });
-
- it('hides the preview DOM element by default', () => {
- panelSpy.mockImplementation();
- instance.togglePreview();
- expect(instance.preview.el.style.display).toBe('none');
- });
- });
-
- describe('preview layout setup', () => {
- it('sets correct preview layout', () => {
- jest.spyOn(instance, 'layout');
- const { width, height } = instance.getLayoutInfo();
-
- instance.togglePreview();
-
- expect(instance.layout).toHaveBeenCalledWith({
- width: width * EXTENSION_MARKDOWN_PREVIEW_PANEL_WIDTH,
- height,
- });
- });
- });
-
- describe('preview panel', () => {
- it('toggles preview CSS class on the editor', () => {
- expect(editorEl.classList.contains(EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS)).toBe(
- false,
- );
- instance.togglePreview();
- expect(editorEl.classList.contains(EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS)).toBe(
- true,
- );
- instance.togglePreview();
- expect(editorEl.classList.contains(EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS)).toBe(
- false,
- );
- });
-
- it('toggles visibility of the preview DOM element', async () => {
- await togglePreview();
- expect(instance.preview.el.style.display).toBe('block');
- await togglePreview();
- expect(instance.preview.el.style.display).toBe('none');
- });
-
- describe('hidden preview DOM element', () => {
- it('listens to model changes and re-fetches preview', async () => {
- expect(mockAxios.history.post).toHaveLength(0);
- await togglePreview();
- expect(mockAxios.history.post).toHaveLength(1);
-
- instance.setValue('New Value');
- await waitForPromises();
- expect(mockAxios.history.post).toHaveLength(2);
- });
-
- it('stores disposable listener for model changes', async () => {
- expect(instance.preview.modelChangeListener).toBeUndefined();
- await togglePreview();
- expect(instance.preview.modelChangeListener).toBeDefined();
- });
- });
-
- describe('already visible preview', () => {
- beforeEach(async () => {
- await togglePreview();
- mockAxios.resetHistory();
- });
-
- it('does not re-fetch the preview', () => {
- instance.togglePreview();
- expect(mockAxios.history.post).toHaveLength(0);
- });
-
- it('disposes the model change event listener', () => {
- const disposeSpy = jest.fn();
- instance.preview.modelChangeListener = {
- dispose: disposeSpy,
- };
- instance.togglePreview();
- expect(disposeSpy).toHaveBeenCalled();
- });
- });
- });
- });
-
describe('getSelectedText', () => {
it('does not fail if there is no selection and returns the empty string', () => {
jest.spyOn(instance, 'getSelection');
diff --git a/spec/frontend/editor/source_editor_markdown_livepreview_ext_spec.js b/spec/frontend/editor/source_editor_markdown_livepreview_ext_spec.js
new file mode 100644
index 00000000000..3d797073c05
--- /dev/null
+++ b/spec/frontend/editor/source_editor_markdown_livepreview_ext_spec.js
@@ -0,0 +1,398 @@
+import MockAdapter from 'axios-mock-adapter';
+import { editor as monacoEditor } from 'monaco-editor';
+import waitForPromises from 'helpers/wait_for_promises';
+import {
+ EXTENSION_MARKDOWN_PREVIEW_PANEL_CLASS,
+ EXTENSION_MARKDOWN_PREVIEW_ACTION_ID,
+ EXTENSION_MARKDOWN_PREVIEW_PANEL_WIDTH,
+ EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS,
+ EXTENSION_MARKDOWN_PREVIEW_UPDATE_DELAY,
+} from '~/editor/constants';
+import { EditorMarkdownPreviewExtension } from '~/editor/extensions/source_editor_markdown_livepreview_ext';
+import SourceEditor from '~/editor/source_editor';
+import createFlash from '~/flash';
+import axios from '~/lib/utils/axios_utils';
+import syntaxHighlight from '~/syntax_highlight';
+
+jest.mock('~/syntax_highlight');
+jest.mock('~/flash');
+
+describe('Markdown Live Preview Extension for Source Editor', () => {
+ let editor;
+ let instance;
+ let editorEl;
+ let panelSpy;
+ let mockAxios;
+ const previewMarkdownPath = '/gitlab/fooGroup/barProj/preview_markdown';
+ const firstLine = 'This is a';
+ const secondLine = 'multiline';
+ const thirdLine = 'string with some **markup**';
+ const text = `${firstLine}\n${secondLine}\n${thirdLine}`;
+ const plaintextPath = 'foo.txt';
+ const markdownPath = 'foo.md';
+ const responseData = '<div>FooBar</div>';
+
+ const togglePreview = async () => {
+ instance.togglePreview();
+ await waitForPromises();
+ };
+
+ beforeEach(() => {
+ mockAxios = new MockAdapter(axios);
+ setFixtures('<div id="editor" data-editor-loading></div>');
+ editorEl = document.getElementById('editor');
+ editor = new SourceEditor();
+ instance = editor.createInstance({
+ el: editorEl,
+ blobPath: markdownPath,
+ blobContent: text,
+ });
+ instance.use(new EditorMarkdownPreviewExtension({ instance, previewMarkdownPath }));
+ panelSpy = jest.spyOn(EditorMarkdownPreviewExtension, 'togglePreviewPanel');
+ });
+
+ afterEach(() => {
+ instance.dispose();
+ editorEl.remove();
+ mockAxios.restore();
+ });
+
+ it('sets up the instance', () => {
+ expect(instance.preview).toEqual({
+ el: undefined,
+ action: expect.any(Object),
+ shown: false,
+ modelChangeListener: undefined,
+ });
+ expect(instance.previewMarkdownPath).toBe(previewMarkdownPath);
+ });
+
+ describe('model language changes listener', () => {
+ let cleanupSpy;
+ let actionSpy;
+
+ beforeEach(async () => {
+ cleanupSpy = jest.spyOn(instance, 'cleanup');
+ actionSpy = jest.spyOn(instance, 'setupPreviewAction');
+ await togglePreview();
+ });
+
+ it('cleans up when switching away from markdown', () => {
+ expect(instance.cleanup).not.toHaveBeenCalled();
+ expect(instance.setupPreviewAction).not.toHaveBeenCalled();
+
+ instance.updateModelLanguage(plaintextPath);
+
+ expect(cleanupSpy).toHaveBeenCalled();
+ expect(actionSpy).not.toHaveBeenCalled();
+ });
+
+ it.each`
+ oldLanguage | newLanguage | setupCalledTimes
+ ${'plaintext'} | ${'markdown'} | ${1}
+ ${'markdown'} | ${'markdown'} | ${0}
+ ${'markdown'} | ${'plaintext'} | ${0}
+ ${'markdown'} | ${undefined} | ${0}
+ ${undefined} | ${'markdown'} | ${1}
+ `(
+ 'correctly handles re-enabling of the action when switching from $oldLanguage to $newLanguage',
+ ({ oldLanguage, newLanguage, setupCalledTimes } = {}) => {
+ expect(actionSpy).not.toHaveBeenCalled();
+ instance.updateModelLanguage(oldLanguage);
+ instance.updateModelLanguage(newLanguage);
+ expect(actionSpy).toHaveBeenCalledTimes(setupCalledTimes);
+ },
+ );
+ });
+
+ describe('model change listener', () => {
+ let cleanupSpy;
+ let actionSpy;
+
+ beforeEach(() => {
+ cleanupSpy = jest.spyOn(instance, 'cleanup');
+ actionSpy = jest.spyOn(instance, 'setupPreviewAction');
+ instance.togglePreview();
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('does not do anything if there is no model', () => {
+ instance.setModel(null);
+
+ expect(cleanupSpy).not.toHaveBeenCalled();
+ expect(actionSpy).not.toHaveBeenCalled();
+ });
+
+ it('cleans up the preview when the model changes', () => {
+ instance.setModel(monacoEditor.createModel('foo'));
+ expect(cleanupSpy).toHaveBeenCalled();
+ });
+
+ it.each`
+ language | setupCalledTimes
+ ${'markdown'} | ${1}
+ ${'plaintext'} | ${0}
+ ${undefined} | ${0}
+ `(
+ 'correctly handles actions when the new model is $language',
+ ({ language, setupCalledTimes } = {}) => {
+ instance.setModel(monacoEditor.createModel('foo', language));
+
+ expect(actionSpy).toHaveBeenCalledTimes(setupCalledTimes);
+ },
+ );
+ });
+
+ describe('cleanup', () => {
+ beforeEach(async () => {
+ mockAxios.onPost().reply(200, { body: responseData });
+ await togglePreview();
+ });
+
+ it('disposes the modelChange listener and does not fetch preview on content changes', () => {
+ expect(instance.preview.modelChangeListener).toBeDefined();
+ jest.spyOn(instance, 'fetchPreview');
+
+ instance.cleanup();
+ instance.setValue('Foo Bar');
+ jest.advanceTimersByTime(EXTENSION_MARKDOWN_PREVIEW_UPDATE_DELAY);
+
+ expect(instance.fetchPreview).not.toHaveBeenCalled();
+ });
+
+ it('removes the contextual menu action', () => {
+ expect(instance.getAction(EXTENSION_MARKDOWN_PREVIEW_ACTION_ID)).toBeDefined();
+
+ instance.cleanup();
+
+ expect(instance.getAction(EXTENSION_MARKDOWN_PREVIEW_ACTION_ID)).toBe(null);
+ });
+
+ it('toggles the `shown` flag', () => {
+ expect(instance.preview.shown).toBe(true);
+ instance.cleanup();
+ expect(instance.preview.shown).toBe(false);
+ });
+
+ it('toggles the panel only if the preview is visible', () => {
+ const { el: previewEl } = instance.preview;
+ const parentEl = previewEl.parentElement;
+
+ expect(previewEl).toBeVisible();
+ expect(parentEl.classList.contains(EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS)).toBe(true);
+
+ instance.cleanup();
+ expect(previewEl).toBeHidden();
+ expect(parentEl.classList.contains(EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS)).toBe(
+ false,
+ );
+
+ instance.cleanup();
+ expect(previewEl).toBeHidden();
+ expect(parentEl.classList.contains(EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS)).toBe(
+ false,
+ );
+ });
+
+ it('toggles the layout only if the preview is visible', () => {
+ const { width } = instance.getLayoutInfo();
+
+ expect(instance.preview.shown).toBe(true);
+
+ instance.cleanup();
+
+ const { width: newWidth } = instance.getLayoutInfo();
+ expect(newWidth === width / EXTENSION_MARKDOWN_PREVIEW_PANEL_WIDTH).toBe(true);
+
+ instance.cleanup();
+ expect(newWidth === width / EXTENSION_MARKDOWN_PREVIEW_PANEL_WIDTH).toBe(true);
+ });
+ });
+
+ describe('fetchPreview', () => {
+ const fetchPreview = async () => {
+ instance.fetchPreview();
+ await waitForPromises();
+ };
+
+ let previewMarkdownSpy;
+
+ beforeEach(() => {
+ previewMarkdownSpy = jest.fn().mockImplementation(() => [200, { body: responseData }]);
+ mockAxios.onPost(previewMarkdownPath).replyOnce((req) => previewMarkdownSpy(req));
+ });
+
+ it('correctly fetches preview based on previewMarkdownPath', async () => {
+ await fetchPreview();
+
+ expect(previewMarkdownSpy).toHaveBeenCalledWith(
+ expect.objectContaining({ data: JSON.stringify({ text }) }),
+ );
+ });
+
+ it('puts the fetched content into the preview DOM element', async () => {
+ instance.preview.el = editorEl.parentElement;
+ await fetchPreview();
+ expect(instance.preview.el.innerHTML).toEqual(responseData);
+ });
+
+ it('applies syntax highlighting to the preview content', async () => {
+ instance.preview.el = editorEl.parentElement;
+ await fetchPreview();
+ expect(syntaxHighlight).toHaveBeenCalled();
+ });
+
+ it('catches the errors when fetching the preview', async () => {
+ mockAxios.onPost().reply(500);
+
+ await fetchPreview();
+ expect(createFlash).toHaveBeenCalled();
+ });
+ });
+
+ describe('setupPreviewAction', () => {
+ it('adds the contextual menu action', () => {
+ expect(instance.getAction(EXTENSION_MARKDOWN_PREVIEW_ACTION_ID)).toBeDefined();
+ });
+
+ it('does not set up action if one already exists', () => {
+ jest.spyOn(instance, 'addAction').mockImplementation();
+
+ instance.setupPreviewAction();
+ expect(instance.addAction).not.toHaveBeenCalled();
+ });
+
+ it('toggles preview when the action is triggered', () => {
+ jest.spyOn(instance, 'togglePreview').mockImplementation();
+
+ expect(instance.togglePreview).not.toHaveBeenCalled();
+
+ const action = instance.getAction(EXTENSION_MARKDOWN_PREVIEW_ACTION_ID);
+ action.run();
+
+ expect(instance.togglePreview).toHaveBeenCalled();
+ });
+ });
+
+ describe('togglePreview', () => {
+ beforeEach(() => {
+ mockAxios.onPost().reply(200, { body: responseData });
+ });
+
+ it('toggles preview flag on instance', () => {
+ expect(instance.preview.shown).toBe(false);
+
+ instance.togglePreview();
+ expect(instance.preview.shown).toBe(true);
+
+ instance.togglePreview();
+ expect(instance.preview.shown).toBe(false);
+ });
+
+ describe('panel DOM element set up', () => {
+ it('sets up an element to contain the preview and stores it on instance', () => {
+ expect(instance.preview.el).toBeUndefined();
+
+ instance.togglePreview();
+
+ expect(instance.preview.el).toBeDefined();
+ expect(instance.preview.el.classList.contains(EXTENSION_MARKDOWN_PREVIEW_PANEL_CLASS)).toBe(
+ true,
+ );
+ });
+
+ it('re-uses existing preview DOM element on repeated calls', () => {
+ instance.togglePreview();
+ const origPreviewEl = instance.preview.el;
+ instance.togglePreview();
+
+ expect(instance.preview.el).toBe(origPreviewEl);
+ });
+
+ it('hides the preview DOM element by default', () => {
+ panelSpy.mockImplementation();
+ instance.togglePreview();
+ expect(instance.preview.el.style.display).toBe('none');
+ });
+ });
+
+ describe('preview layout setup', () => {
+ it('sets correct preview layout', () => {
+ jest.spyOn(instance, 'layout');
+ const { width, height } = instance.getLayoutInfo();
+
+ instance.togglePreview();
+
+ expect(instance.layout).toHaveBeenCalledWith({
+ width: width * EXTENSION_MARKDOWN_PREVIEW_PANEL_WIDTH,
+ height,
+ });
+ });
+ });
+
+ describe('preview panel', () => {
+ it('toggles preview CSS class on the editor', () => {
+ expect(editorEl.classList.contains(EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS)).toBe(
+ false,
+ );
+ instance.togglePreview();
+ expect(editorEl.classList.contains(EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS)).toBe(
+ true,
+ );
+ instance.togglePreview();
+ expect(editorEl.classList.contains(EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS)).toBe(
+ false,
+ );
+ });
+
+ it('toggles visibility of the preview DOM element', async () => {
+ await togglePreview();
+ expect(instance.preview.el.style.display).toBe('block');
+ await togglePreview();
+ expect(instance.preview.el.style.display).toBe('none');
+ });
+
+ describe('hidden preview DOM element', () => {
+ it('listens to model changes and re-fetches preview', async () => {
+ expect(mockAxios.history.post).toHaveLength(0);
+ await togglePreview();
+ expect(mockAxios.history.post).toHaveLength(1);
+
+ instance.setValue('New Value');
+ await waitForPromises();
+ expect(mockAxios.history.post).toHaveLength(2);
+ });
+
+ it('stores disposable listener for model changes', async () => {
+ expect(instance.preview.modelChangeListener).toBeUndefined();
+ await togglePreview();
+ expect(instance.preview.modelChangeListener).toBeDefined();
+ });
+ });
+
+ describe('already visible preview', () => {
+ beforeEach(async () => {
+ await togglePreview();
+ mockAxios.resetHistory();
+ });
+
+ it('does not re-fetch the preview', () => {
+ instance.togglePreview();
+ expect(mockAxios.history.post).toHaveLength(0);
+ });
+
+ it('disposes the model change event listener', () => {
+ const disposeSpy = jest.fn();
+ instance.preview.modelChangeListener = {
+ dispose: disposeSpy,
+ };
+ instance.togglePreview();
+ expect(disposeSpy).toHaveBeenCalled();
+ });
+ });
+ });
+ });
+});
diff --git a/spec/helpers/notify_helper_spec.rb b/spec/helpers/notify_helper_spec.rb
index a4193444528..633a4b65139 100644
--- a/spec/helpers/notify_helper_spec.rb
+++ b/spec/helpers/notify_helper_spec.rb
@@ -70,28 +70,6 @@ RSpec.describe NotifyHelper do
expect(helper.invited_join_url(token, member))
.to eq("http://test.host/-/invites/#{token}?experiment_name=invite_email_preview_text&invite_type=initial_email")
end
-
- context 'when invite_email_from is enabled' do
- before do
- stub_experiments(invite_email_from: :control)
- end
-
- it 'has correct params' do
- expect(helper.invited_join_url(token, member))
- .to eq("http://test.host/-/invites/#{token}?experiment_name=invite_email_from&invite_type=initial_email")
- end
- end
- end
-
- context 'when invite_email_from is enabled' do
- before do
- stub_experiments(invite_email_from: :control)
- end
-
- it 'has correct params' do
- expect(helper.invited_join_url(token, member))
- .to eq("http://test.host/-/invites/#{token}?experiment_name=invite_email_from&invite_type=initial_email")
- end
end
context 'when invite_email_preview_text is disabled' do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb
new file mode 100644
index 00000000000..28bc685286f
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Pipeline::Chain::CreateDeployments do
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
+
+ let(:stage) { build(:ci_stage_entity, project: project, statuses: [job]) }
+ let(:pipeline) { create(:ci_pipeline, project: project, stages: [stage]) }
+
+ let(:command) do
+ Gitlab::Ci::Pipeline::Chain::Command.new(project: project, current_user: user)
+ end
+
+ let(:step) { described_class.new(pipeline, command) }
+
+ describe '#perform!' do
+ subject { step.perform! }
+
+ before do
+ job.pipeline = pipeline
+ end
+
+ context 'when a pipeline contains a deployment job' do
+ let!(:job) { build(:ci_build, :start_review_app, project: project) }
+ let!(:environment) { create(:environment, project: project, name: job.expanded_environment_name) }
+
+ it 'creates a deployment record' do
+ expect { subject }.to change { Deployment.count }.by(1)
+
+ job.reset
+ expect(job.deployment.project).to eq(job.project)
+ expect(job.deployment.ref).to eq(job.ref)
+ expect(job.deployment.sha).to eq(job.sha)
+ expect(job.deployment.deployable).to eq(job)
+ expect(job.deployment.deployable_type).to eq('CommitStatus')
+ expect(job.deployment.environment).to eq(job.persisted_environment)
+ end
+
+ context 'when creation failure occures' do
+ before do
+ allow_next_instance_of(Deployment) do |deployment|
+ allow(deployment).to receive(:save!) { raise ActiveRecord::RecordInvalid }
+ end
+ end
+
+ it 'trackes the exception' do
+ expect { subject }.to raise_error(described_class::DeploymentCreationError)
+
+ expect(Deployment.count).to eq(0)
+ end
+ end
+
+ context 'when the corresponding environment does not exist' do
+ let!(:environment) { }
+
+ it 'does not create a deployment record' do
+ expect { subject }.not_to change { Deployment.count }
+
+ expect(job.deployment).to be_nil
+ end
+ end
+
+ context 'when create_deployment_in_separate_transaction feature flag is disabled' do
+ before do
+ stub_feature_flags(create_deployment_in_separate_transaction: false)
+ end
+
+ it 'does not create a deployment record' do
+ expect { subject }.not_to change { Deployment.count }
+
+ expect(job.deployment).to be_nil
+ end
+ end
+ end
+
+ context 'when a pipeline contains a teardown job' do
+ let!(:job) { build(:ci_build, :stop_review_app, project: project) }
+ let!(:environment) { create(:environment, name: job.expanded_environment_name) }
+
+ it 'does not create a deployment record' do
+ expect { subject }.not_to change { Deployment.count }
+
+ expect(job.deployment).to be_nil
+ end
+ end
+
+ context 'when a pipeline does not contain a deployment job' do
+ let!(:job) { build(:ci_build, project: project) }
+
+ it 'does not create any deployments' do
+ expect { subject }.not_to change { Deployment.count }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb
new file mode 100644
index 00000000000..253928e1a19
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb
@@ -0,0 +1,94 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Pipeline::Chain::EnsureEnvironments do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:stage) { build(:ci_stage_entity, project: project, statuses: [job]) }
+ let(:pipeline) { build(:ci_pipeline, project: project, stages: [stage]) }
+
+ let(:command) do
+ Gitlab::Ci::Pipeline::Chain::Command.new(project: project, current_user: user)
+ end
+
+ let(:step) { described_class.new(pipeline, command) }
+
+ describe '#perform!' do
+ subject { step.perform! }
+
+ before do
+ job.pipeline = pipeline
+ end
+
+ context 'when a pipeline contains a deployment job' do
+ let!(:job) { build(:ci_build, :start_review_app, project: project) }
+
+ it 'ensures environment existence for the job' do
+ expect { subject }.to change { Environment.count }.by(1)
+
+ expect(project.environments.find_by_name('review/master')).to be_present
+ expect(job.persisted_environment.name).to eq('review/master')
+ expect(job.metadata.expanded_environment_name).to eq('review/master')
+ end
+
+ context 'when an environment has already been existed' do
+ before do
+ create(:environment, project: project, name: 'review/master')
+ end
+
+ it 'ensures environment existence for the job' do
+ expect { subject }.not_to change { Environment.count }
+
+ expect(project.environments.find_by_name('review/master')).to be_present
+ expect(job.persisted_environment.name).to eq('review/master')
+ expect(job.metadata.expanded_environment_name).to eq('review/master')
+ end
+ end
+
+ context 'when an environment name contains an invalid character' do
+ let(:pipeline) { build(:ci_pipeline, ref: '!!!', project: project, stages: [stage]) }
+
+ it 'sets the failure status' do
+ expect { subject }.not_to change { Environment.count }
+
+ expect(job).to be_failed
+ expect(job).to be_environment_creation_failure
+ expect(job.persisted_environment).to be_nil
+ end
+ end
+
+ context 'when create_deployment_in_separate_transaction feature flag is disabled' do
+ before do
+ stub_feature_flags(create_deployment_in_separate_transaction: false)
+ end
+
+ it 'does not create any environments' do
+ expect { subject }.not_to change { Environment.count }
+
+ expect(job.persisted_environment).to be_nil
+ end
+ end
+ end
+
+ context 'when a pipeline contains a teardown job' do
+ let!(:job) { build(:ci_build, :stop_review_app, project: project) }
+
+ it 'ensures environment existence for the job' do
+ expect { subject }.to change { Environment.count }.by(1)
+
+ expect(project.environments.find_by_name('review/master')).to be_present
+ expect(job.persisted_environment.name).to eq('review/master')
+ expect(job.metadata.expanded_environment_name).to eq('review/master')
+ end
+ end
+
+ context 'when a pipeline does not contain a deployment job' do
+ let!(:job) { build(:ci_build, project: project) }
+
+ it 'does not create any environments' do
+ expect { subject }.not_to change { Environment.count }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/ensure_resource_groups_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/ensure_resource_groups_spec.rb
new file mode 100644
index 00000000000..87df5a3e21b
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/chain/ensure_resource_groups_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Pipeline::Chain::EnsureResourceGroups do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:stage) { build(:ci_stage_entity, project: project, statuses: [job]) }
+ let(:pipeline) { build(:ci_pipeline, project: project, stages: [stage]) }
+ let!(:environment) { create(:environment, name: 'production', project: project) }
+
+ let(:command) do
+ Gitlab::Ci::Pipeline::Chain::Command.new(project: project, current_user: user)
+ end
+
+ let(:step) { described_class.new(pipeline, command) }
+
+ describe '#perform!' do
+ subject { step.perform! }
+
+ before do
+ job.pipeline = pipeline
+ end
+
+ context 'when a pipeline contains a job that requires a resource group' do
+ let!(:job) do
+ build(:ci_build, project: project, environment: 'production', options: { resource_group_key: '$CI_ENVIRONMENT_NAME' })
+ end
+
+ it 'ensures the resource group existence' do
+ expect { subject }.to change { Ci::ResourceGroup.count }.by(1)
+
+ expect(project.resource_groups.find_by_key('production')).to be_present
+ expect(job.resource_group.key).to eq('production')
+ expect(job.options[:resource_group_key]).to be_nil
+ end
+
+ context 'when a resource group has already been existed' do
+ before do
+ create(:ci_resource_group, project: project, key: 'production')
+ end
+
+ it 'ensures the resource group existence' do
+ expect { subject }.not_to change { Ci::ResourceGroup.count }
+
+ expect(project.resource_groups.find_by_key('production')).to be_present
+ expect(job.resource_group.key).to eq('production')
+ expect(job.options[:resource_group_key]).to be_nil
+ end
+ end
+
+ context 'when a resource group key contains an invalid character' do
+ let!(:job) do
+ build(:ci_build, project: project, environment: '!!!', options: { resource_group_key: '$CI_ENVIRONMENT_NAME' })
+ end
+
+ it 'does not create any resource groups' do
+ expect { subject }.not_to change { Ci::ResourceGroup.count }
+
+ expect(job.resource_group).to be_nil
+ end
+ end
+
+ context 'when create_deployment_in_separate_transaction feature flag is disabled' do
+ before do
+ stub_feature_flags(create_deployment_in_separate_transaction: false)
+ end
+
+ it 'does not create any resource groups' do
+ expect { subject }.not_to change { Ci::ResourceGroup.count }
+
+ expect(job.resource_group).to be_nil
+ end
+ end
+ end
+
+ context 'when a pipeline does not contain a job that requires a resource group' do
+ let!(:job) { build(:ci_build, project: project) }
+
+ it 'does not create any resource groups' do
+ expect { subject }.not_to change { Ci::ResourceGroup.count }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
index 73d827085b8..c53f1be1057 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
@@ -393,6 +393,10 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
describe '#to_resource' do
subject { seed_build.to_resource }
+ before do
+ stub_feature_flags(create_deployment_in_separate_transaction: false)
+ end
+
context 'when job is Ci::Build' do
it { is_expected.to be_a(::Ci::Build) }
it { is_expected.to be_valid }
@@ -443,6 +447,18 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
it_behaves_like 'deployment job'
it_behaves_like 'ensures environment existence'
+ context 'when create_deployment_in_separate_transaction feature flag is enabled' do
+ before do
+ stub_feature_flags(create_deployment_in_separate_transaction: true)
+ end
+
+ it 'does not create any deployments nor environments' do
+ expect(subject.deployment).to be_nil
+ expect(Environment.count).to eq(0)
+ expect(Deployment.count).to eq(0)
+ end
+ end
+
context 'when the environment name is invalid' do
let(:attributes) { { name: 'deploy', ref: 'master', environment: '!!!' } }
@@ -496,6 +512,18 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
it 'returns a job with resource group' do
expect(subject.resource_group).not_to be_nil
expect(subject.resource_group.key).to eq('iOS')
+ expect(Ci::ResourceGroup.count).to eq(1)
+ end
+
+ context 'when create_deployment_in_separate_transaction feature flag is enabled' do
+ before do
+ stub_feature_flags(create_deployment_in_separate_transaction: true)
+ end
+
+ it 'does not create any resource groups' do
+ expect(subject.resource_group).to be_nil
+ expect(Ci::ResourceGroup.count).to eq(0)
+ end
end
context 'when resource group has $CI_ENVIRONMENT_NAME in it' do
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index c3c35279fe8..66cb95a59a6 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -199,7 +199,6 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
for_defined_days_back do
user = create(:user)
user2 = create(:user)
- create(:event, author: user)
create(:group_member, user: user)
create(:authentication_event, user: user, provider: :ldapmain, result: :success)
create(:authentication_event, user: user2, provider: :ldapsecondary, result: :success)
@@ -208,17 +207,24 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
create(:authentication_event, user: user, provider: :group_saml, result: :failed)
end
+ for_defined_days_back(days: [31, 29, 3]) do
+ create(:event)
+ end
+
+ stub_const('Gitlab::Database::PostgresHll::BatchDistinctCounter::DEFAULT_BATCH_SIZE', 1)
+ stub_const('Gitlab::Database::PostgresHll::BatchDistinctCounter::MIN_REQUIRED_BATCH_SIZE', 0)
+
expect(described_class.usage_activity_by_stage_manage({})).to include(
events: -1,
groups: 2,
- users_created: 6,
+ users_created: 10,
omniauth_providers: ['google_oauth2'],
user_auth_by_provider: { 'group_saml' => 2, 'ldap' => 4, 'standard' => 0, 'two-factor' => 0, 'two-factor-via-u2f-device' => 0, "two-factor-via-webauthn-device" => 0 }
)
expect(described_class.usage_activity_by_stage_manage(described_class.monthly_time_range_db_params)).to include(
- events: be_within(error_rate).percent_of(1),
+ events: be_within(error_rate).percent_of(2),
groups: 1,
- users_created: 3,
+ users_created: 6,
omniauth_providers: ['google_oauth2'],
user_auth_by_provider: { 'group_saml' => 1, 'ldap' => 2, 'standard' => 0, 'two-factor' => 0, 'two-factor-via-u2f-device' => 0, "two-factor-via-webauthn-device" => 0 }
)
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 098ac21eb18..ad9bd0faf8a 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -888,27 +888,6 @@ RSpec.describe Notify do
end
end
- context 'with invite_email_from enabled', :experiment do
- before do
- stub_experiments(invite_email_from: :control)
- end
-
- it 'has the correct invite_url with params' do
- is_expected.to have_link('Join now',
- href: invite_url(project_member.invite_token,
- invite_type: Emails::Members::INITIAL_INVITE,
- experiment_name: 'invite_email_from'))
- end
-
- it 'tracks the sent invite' do
- expect(experiment(:invite_email_from)).to track(:assignment)
- .with_context(actor: project_member)
- .on_next_instance
-
- invite_email.deliver_now
- end
- end
-
context 'when invite email sent is tracked', :snowplow do
it 'tracks the sent invite' do
invite_email.deliver_now
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 9aa67057fca..730d7f02424 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -3525,19 +3525,7 @@ RSpec.describe User do
subject { user.membership_groups }
- shared_examples 'returns groups where the user is a member' do
- specify { is_expected.to contain_exactly(parent_group, child_group) }
- end
-
- it_behaves_like 'returns groups where the user is a member'
-
- context 'when feature flag :linear_user_membership_groups is disabled' do
- before do
- stub_feature_flags(linear_user_membership_groups: false)
- end
-
- it_behaves_like 'returns groups where the user is a member'
- end
+ specify { is_expected.to contain_exactly(parent_group, child_group) }
end
describe '#authorizations_for_projects' do