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>2022-08-08 21:11:24 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-08-08 21:11:24 +0300
commit70b375c29f67bdc8bd7e8ade1d5355444106482d (patch)
tree7e4fdec178464a016953a9aecfd349441edb9f44 /spec
parent3de2ce7c6b536d63ea2f93239022eb51fa9241c1 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/search_controller_spec.rb49
-rw-r--r--spec/features/projects/new_project_spec.rb32
-rw-r--r--spec/frontend/ci_variable_list/components/ci_environments_dropdown_spec.js139
-rw-r--r--spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js380
-rw-r--r--spec/frontend/ci_variable_list/mocks.js116
-rw-r--r--spec/frontend/ci_variable_list/utils_spec.js68
-rw-r--r--spec/frontend/content_editor/remark_markdown_processing_spec.js2
-rw-r--r--spec/frontend/labels/labels_select_spec.js6
-rw-r--r--spec/frontend/lib/utils/url_utility_spec.js14
-rw-r--r--spec/frontend/projects/project_new_spec.js67
-rw-r--r--spec/frontend/vue_merge_request_widget/components/mr_widget_memory_usage_spec.js12
-rw-r--r--spec/lib/gitlab/github_import/importer/events/base_importer_spec.rb4
-rw-r--r--spec/lib/gitlab/github_import/importer/events/changed_assignee_spec.rb14
-rw-r--r--spec/lib/gitlab/github_import/importer/events/changed_label_spec.rb13
-rw-r--r--spec/lib/gitlab/github_import/importer/events/changed_milestone_spec.rb13
-rw-r--r--spec/lib/gitlab/github_import/importer/events/closed_spec.rb12
-rw-r--r--spec/lib/gitlab/github_import/importer/events/cross_referenced_spec.rb31
-rw-r--r--spec/lib/gitlab/github_import/importer/events/renamed_spec.rb12
-rw-r--r--spec/lib/gitlab/github_import/importer/events/reopened_spec.rb12
-rw-r--r--spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/issue_events_importer_spec.rb122
-rw-r--r--spec/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer_spec.rb4
-rw-r--r--spec/lib/gitlab/github_import/issuable_finder_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/representation/issue_event_spec.rb30
-rw-r--r--spec/support/shared_examples/controllers/search_cross_project_authorization_shared_examples.rb20
-rw-r--r--spec/support/shared_examples/controllers/search_external_authorization_service_shared_examples.rb24
-rw-r--r--spec/workers/gitlab/github_import/stage/import_issue_events_worker_spec.rb53
27 files changed, 1116 insertions, 137 deletions
diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb
index e03a8b7d321..14b198dbefe 100644
--- a/spec/controllers/search_controller_spec.rb
+++ b/spec/controllers/search_controller_spec.rb
@@ -12,47 +12,6 @@ RSpec.describe SearchController do
sign_in(user)
end
- shared_examples_for 'when the user cannot read cross project' do |action, params|
- before do
- allow(Ability).to receive(:allowed?).and_call_original
- allow(Ability).to receive(:allowed?)
- .with(user, :read_cross_project, :global) { false }
- end
-
- it 'blocks access without a project_id' do
- get action, params: params
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
-
- it 'allows access with a project_id' do
- get action, params: params.merge(project_id: create(:project, :public).id)
-
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
-
- shared_examples_for 'with external authorization service enabled' do |action, params|
- let(:project) { create(:project, namespace: user.namespace) }
- let(:note) { create(:note_on_issue, project: project) }
-
- before do
- enable_external_authorization_service_check
- end
-
- it 'renders a 403 when no project is given' do
- get action, params: params
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
-
- it 'renders a 200 when a project was set' do
- get action, params: params.merge(project_id: project.id)
-
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
-
shared_examples_for 'support for active record query timeouts' do |action, params, method_to_stub, format|
before do
allow_next_instance_of(SearchService) do |service|
@@ -133,10 +92,11 @@ RSpec.describe SearchController do
{
chars_under_limit: (('a' * (term_char_limit - 1) + ' ') * (term_limit - 1))[0, char_limit],
chars_over_limit: (('a' * (term_char_limit - 1) + ' ') * (term_limit - 1))[0, char_limit + 1],
- terms_under_limit: ('abc ' * (term_limit - 1)),
+ terms_under_limit: ('abc ' * (term_limit - 1)),
terms_over_limit: ('abc ' * (term_limit + 1)),
term_length_over_limit: ('a' * (term_char_limit + 1)),
- term_length_under_limit: ('a' * (term_char_limit - 1))
+ term_length_under_limit: ('a' * (term_char_limit - 1)),
+ blank: ''
}
end
@@ -147,6 +107,7 @@ RSpec.describe SearchController do
:terms_over_limit | :set_terms_flash
:term_length_under_limit | :not_to_set_flash
:term_length_over_limit | :not_to_set_flash # abuse, so do nothing.
+ :blank | :not_to_set_flash
end
with_them do
@@ -417,7 +378,7 @@ RSpec.describe SearchController do
expect(payload[:metadata]['meta.search.project_ids']).to eq(%w(456 789))
expect(payload[:metadata]['meta.search.type']).to eq('basic')
expect(payload[:metadata]['meta.search.level']).to eq('global')
- expect(payload[:metadata]['meta.search.language']).to eq('ruby')
+ expect(payload[:metadata]['meta.search.filters.language']).to eq('ruby')
end
get :show, params: {
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index 9d2d1454d77..f45025d079a 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -3,16 +3,44 @@
require 'spec_helper'
RSpec.describe 'New project', :js do
- include Select2Helper
include Spec::Support::Helpers::Features::TopNavSpecHelpers
context 'as a user' do
- let(:user) { create(:user) }
+ let_it_be(:user) { create(:user) }
before do
sign_in(user)
end
+ it 'shows the project description field when it should' do
+ description_label = 'Project description (optional)'
+
+ visit new_project_path
+ click_link 'Create blank project'
+
+ page.within('#blank-project-pane') do
+ expect(page).not_to have_content(description_label)
+ end
+
+ visit new_project_path
+ click_link 'Import project'
+
+ page.within('#import-project-pane') do
+ click_button 'Repository by URL'
+
+ expect(page).to have_content(description_label)
+ end
+
+ visit new_project_path
+ click_link 'Create from template'
+
+ page.within('#create-from-template-pane') do
+ find("[data-testid='use_template_#{Gitlab::ProjectTemplate.localized_templates_table.first.name}']").click
+
+ expect(page).to have_content(description_label)
+ end
+ end
+
it 'shows a message if multiple levels are restricted' do
Gitlab::CurrentSettings.update!(
restricted_visibility_levels: [Gitlab::VisibilityLevel::PRIVATE, Gitlab::VisibilityLevel::INTERNAL]
diff --git a/spec/frontend/ci_variable_list/components/ci_environments_dropdown_spec.js b/spec/frontend/ci_variable_list/components/ci_environments_dropdown_spec.js
new file mode 100644
index 00000000000..e9966576cab
--- /dev/null
+++ b/spec/frontend/ci_variable_list/components/ci_environments_dropdown_spec.js
@@ -0,0 +1,139 @@
+import { GlDropdown, GlDropdownItem, GlIcon, GlSearchBoxByType } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
+import { allEnvironments } from '~/ci_variable_list/constants';
+import CiEnvironmentsDropdown from '~/ci_variable_list/components/ci_environments_dropdown.vue';
+
+describe('Ci environments dropdown', () => {
+ let wrapper;
+
+ const envs = ['dev', 'prod', 'staging'];
+ const defaultProps = { environments: envs, selectedEnvironmentScope: '' };
+
+ const findDropdownText = () => wrapper.findComponent(GlDropdown).text();
+ const findAllDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
+ const findDropdownItemByIndex = (index) => wrapper.findAllComponents(GlDropdownItem).at(index);
+ const findActiveIconByIndex = (index) => findDropdownItemByIndex(index).findComponent(GlIcon);
+ const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
+
+ const createComponent = ({ props = {}, searchTerm = '' } = {}) => {
+ wrapper = mount(CiEnvironmentsDropdown, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ });
+
+ findSearchBox().vm.$emit('input', searchTerm);
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('No environments found', () => {
+ beforeEach(() => {
+ createComponent({ searchTerm: 'stable' });
+ });
+
+ it('renders create button with search term if environments do not contain search term', () => {
+ expect(findAllDropdownItems()).toHaveLength(2);
+ expect(findDropdownItemByIndex(1).text()).toBe('Create wildcard: stable');
+ });
+
+ it('renders empty results message', () => {
+ expect(findDropdownItemByIndex(0).text()).toBe('No matching results');
+ });
+ });
+
+ describe('Search term is empty', () => {
+ beforeEach(() => {
+ createComponent({ props: { environments: envs } });
+ });
+
+ it('renders all environments when search term is empty', () => {
+ expect(findAllDropdownItems()).toHaveLength(3);
+ expect(findDropdownItemByIndex(0).text()).toBe(envs[0]);
+ expect(findDropdownItemByIndex(1).text()).toBe(envs[1]);
+ expect(findDropdownItemByIndex(2).text()).toBe(envs[2]);
+ });
+
+ it('should not display active checkmark on the inactive stage', () => {
+ expect(findActiveIconByIndex(0).classes('gl-visibility-hidden')).toBe(true);
+ });
+ });
+
+ describe('when `*` is the value of selectedEnvironmentScope props', () => {
+ const wildcardScope = '*';
+
+ beforeEach(() => {
+ createComponent({ props: { selectedEnvironmentScope: wildcardScope } });
+ });
+
+ it('shows the `All environments` text and not the wildcard', () => {
+ expect(findDropdownText()).toContain(allEnvironments.text);
+ expect(findDropdownText()).not.toContain(wildcardScope);
+ });
+ });
+
+ describe('Environments found', () => {
+ const currentEnv = envs[2];
+
+ beforeEach(async () => {
+ createComponent({ searchTerm: currentEnv });
+ await nextTick();
+ });
+
+ it('renders only the environment searched for', () => {
+ expect(findAllDropdownItems()).toHaveLength(1);
+ expect(findDropdownItemByIndex(0).text()).toBe(currentEnv);
+ });
+
+ it('should not display create button', () => {
+ const environments = findAllDropdownItems().filter((env) => env.text().startsWith('Create'));
+ expect(environments).toHaveLength(0);
+ expect(findAllDropdownItems()).toHaveLength(1);
+ });
+
+ it('should not display empty results message', () => {
+ expect(wrapper.findComponent({ ref: 'noMatchingResults' }).exists()).toBe(false);
+ });
+
+ it('should clear the search term when showing the dropdown', () => {
+ wrapper.findComponent(GlDropdown).trigger('click');
+
+ expect(findSearchBox().text()).toBe('');
+ });
+
+ describe('Custom events', () => {
+ describe('when clicking on an environment', () => {
+ const itemIndex = 0;
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('should emit `select-environment` if an environment is clicked', async () => {
+ await nextTick();
+
+ await findDropdownItemByIndex(itemIndex).vm.$emit('click');
+
+ expect(wrapper.emitted('select-environment')).toEqual([[envs[itemIndex]]]);
+ });
+ });
+
+ describe('when creating a new environment from a search term', () => {
+ const search = 'new-env';
+ beforeEach(() => {
+ createComponent({ searchTerm: search });
+ });
+
+ it('should emit createClicked if an environment is clicked', async () => {
+ await nextTick();
+ findDropdownItemByIndex(1).vm.$emit('click');
+ expect(wrapper.emitted('create-environment-scope')).toEqual([[search]]);
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js b/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js
new file mode 100644
index 00000000000..51b902d97dc
--- /dev/null
+++ b/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js
@@ -0,0 +1,380 @@
+import { GlButton, GlFormInput } from '@gitlab/ui';
+import { mockTracking } from 'helpers/tracking_helper';
+import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
+import CiEnvironmentsDropdown from '~/ci_variable_list/components/ci_environments_dropdown.vue';
+import CiVariableModal from '~/ci_variable_list/components/ci_variable_modal.vue';
+import {
+ ADD_VARIABLE_ACTION,
+ AWS_ACCESS_KEY_ID,
+ EDIT_VARIABLE_ACTION,
+ EVENT_LABEL,
+ EVENT_ACTION,
+ ENVIRONMENT_SCOPE_LINK_TITLE,
+} from '~/ci_variable_list/constants';
+import { mockVariablesWithScopes } from '../mocks';
+import ModalStub from '../stubs';
+
+describe('Ci variable modal', () => {
+ let wrapper;
+ let trackingSpy;
+
+ const maskableRegex = '^[a-zA-Z0-9_+=/@:.~-]{8,}$';
+
+ const defaultProvide = {
+ awsLogoSvgPath: '/logo',
+ awsTipCommandsLink: '/tips',
+ awsTipDeployLink: '/deploy',
+ awsTipLearnLink: '/learn-link',
+ containsVariableReferenceLink: '/reference',
+ environmentScopeLink: '/help/environments',
+ isProtectedByDefault: false,
+ maskedEnvironmentVariablesLink: '/variables-link',
+ maskableRegex,
+ protectedEnvironmentVariablesLink: '/protected-link',
+ };
+
+ const defaultProps = {
+ areScopedVariablesAvailable: true,
+ environments: [],
+ mode: ADD_VARIABLE_ACTION,
+ selectedVariable: {},
+ };
+
+ const createComponent = ({ mountFn = shallowMountExtended, props = {}, provide = {} } = {}) => {
+ wrapper = mountFn(CiVariableModal, {
+ attachTo: document.body,
+ provide: { ...defaultProvide, ...provide },
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ stubs: {
+ GlModal: ModalStub,
+ },
+ });
+ };
+
+ const findCiEnvironmentsDropdown = () => wrapper.find(CiEnvironmentsDropdown);
+ const findReferenceWarning = () => wrapper.findByTestId('contains-variable-reference');
+ const findModal = () => wrapper.find(ModalStub);
+ const findAWSTip = () => wrapper.findByTestId('aws-guidance-tip');
+ const findAddorUpdateButton = () => wrapper.findByTestId('ciUpdateOrAddVariableBtn');
+ const deleteVariableButton = () =>
+ findModal()
+ .findAll(GlButton)
+ .wrappers.find((button) => button.props('variant') === 'danger');
+ const findProtectedVariableCheckbox = () =>
+ wrapper.findByTestId('ci-variable-protected-checkbox');
+ const findMaskedVariableCheckbox = () => wrapper.findByTestId('ci-variable-masked-checkbox');
+ const findValueField = () => wrapper.find('#ci-variable-value');
+ const findEnvScopeLink = () => wrapper.findByTestId('environment-scope-link');
+ const findEnvScopeInput = () => wrapper.findByTestId('environment-scope').find(GlFormInput);
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('Adding a variable', () => {
+ describe('when no key/value pair are present', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('shows the submit button as disabled ', () => {
+ expect(findAddorUpdateButton().attributes('disabled')).toBeTruthy();
+ });
+ });
+
+ describe('when a key/value pair is present', () => {
+ beforeEach(() => {
+ createComponent({ props: { selectedVariable: mockVariablesWithScopes[0] } });
+ });
+
+ it('shows the submit button as enabled ', () => {
+ expect(findAddorUpdateButton().attributes('disabled')).toBeFalsy();
+ });
+ });
+
+ describe('events', () => {
+ const [currentVariable] = mockVariablesWithScopes;
+
+ beforeEach(() => {
+ createComponent({ props: { selectedVariable: currentVariable } });
+ jest.spyOn(wrapper.vm, '$emit');
+ });
+
+ it('Dispatches `add-variable` action on submit', () => {
+ findAddorUpdateButton().vm.$emit('click');
+ expect(wrapper.emitted('add-variable')).toEqual([[currentVariable]]);
+ });
+
+ it('Dispatches the `hideModal` event when dismissing', () => {
+ findModal().vm.$emit('hidden');
+ expect(wrapper.emitted('hideModal')).toEqual([[]]);
+ });
+ });
+ });
+
+ describe('when protected by default', () => {
+ describe('when adding a new variable', () => {
+ beforeEach(() => {
+ createComponent({ provide: { isProtectedByDefault: true } });
+ findModal().vm.$emit('shown');
+ });
+
+ it('updates the protected value to true', () => {
+ expect(
+ findProtectedVariableCheckbox().attributes('data-is-protected-checked'),
+ ).toBeTruthy();
+ });
+ });
+
+ describe('when editing a variable', () => {
+ beforeEach(() => {
+ createComponent({
+ provide: { isProtectedByDefault: false },
+ props: {
+ selectedVariable: {},
+ mode: EDIT_VARIABLE_ACTION,
+ },
+ });
+ findModal().vm.$emit('shown');
+ });
+
+ it('keeps the value as false', async () => {
+ expect(
+ findProtectedVariableCheckbox().attributes('data-is-protected-checked'),
+ ).toBeUndefined();
+ });
+ });
+ });
+
+ describe('Adding a new non-AWS variable', () => {
+ beforeEach(() => {
+ const [variable] = mockVariablesWithScopes;
+ createComponent({ mountFn: mountExtended, props: { selectedVariable: variable } });
+ });
+
+ it('does not show AWS guidance tip', () => {
+ const tip = findAWSTip();
+ expect(tip.exists()).toBe(true);
+ expect(tip.isVisible()).toBe(false);
+ });
+ });
+
+ describe('Adding a new AWS variable', () => {
+ beforeEach(() => {
+ const [variable] = mockVariablesWithScopes;
+ const AWSKeyVariable = {
+ ...variable,
+ key: AWS_ACCESS_KEY_ID,
+ value: 'AKIAIOSFODNN7EXAMPLEjdhy',
+ };
+ createComponent({ mountFn: mountExtended, props: { selectedVariable: AWSKeyVariable } });
+ });
+
+ it('shows AWS guidance tip', () => {
+ const tip = findAWSTip();
+ expect(tip.exists()).toBe(true);
+ expect(tip.isVisible()).toBe(true);
+ });
+ });
+
+ describe('Reference warning when adding a variable', () => {
+ describe('with a $ character', () => {
+ beforeEach(() => {
+ const [variable] = mockVariablesWithScopes;
+ const variableWithDollarSign = {
+ ...variable,
+ value: 'valueWith$',
+ };
+ createComponent({
+ mountFn: mountExtended,
+ props: { selectedVariable: variableWithDollarSign },
+ });
+ });
+
+ it(`renders the variable reference warning`, () => {
+ expect(findReferenceWarning().exists()).toBe(true);
+ });
+ });
+
+ describe('without a $ character', () => {
+ beforeEach(() => {
+ const [variable] = mockVariablesWithScopes;
+ createComponent({
+ mountFn: mountExtended,
+ props: { selectedVariable: variable },
+ });
+ });
+
+ it(`does not render the variable reference warning`, () => {
+ expect(findReferenceWarning().exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('Editing a variable', () => {
+ const [variable] = mockVariablesWithScopes;
+
+ beforeEach(() => {
+ createComponent({ props: { selectedVariable: variable, mode: EDIT_VARIABLE_ACTION } });
+ jest.spyOn(wrapper.vm, '$emit');
+ });
+
+ it('button text is Update variable when updating', () => {
+ expect(findAddorUpdateButton().text()).toBe('Update variable');
+ });
+
+ it('Update variable button dispatches updateVariable with correct variable', () => {
+ findAddorUpdateButton().vm.$emit('click');
+ expect(wrapper.emitted('update-variable')).toEqual([[variable]]);
+ });
+
+ it('Propagates the `hideModal` event', () => {
+ findModal().vm.$emit('hidden');
+ expect(wrapper.emitted('hideModal')).toEqual([[]]);
+ });
+
+ it('dispatches `delete-variable` with correct variable to delete', () => {
+ deleteVariableButton().vm.$emit('click');
+ expect(wrapper.emitted('delete-variable')).toEqual([[variable]]);
+ });
+ });
+
+ describe('Environment scope', () => {
+ describe('when feature is available', () => {
+ it('renders the environment dropdown', () => {
+ createComponent({
+ mountFn: mountExtended,
+ props: {
+ areScopedVariablesAvailable: true,
+ },
+ });
+
+ expect(findCiEnvironmentsDropdown().exists()).toBe(true);
+ expect(findCiEnvironmentsDropdown().isVisible()).toBe(true);
+ });
+
+ it('renders a link to documentation on scopes', () => {
+ createComponent({ mountFn: mountExtended });
+
+ const link = findEnvScopeLink();
+
+ expect(link.attributes('title')).toBe(ENVIRONMENT_SCOPE_LINK_TITLE);
+ expect(link.attributes('href')).toBe(defaultProvide.environmentScopeLink);
+ });
+ });
+
+ describe('when feature is not available', () => {
+ it('disables the dropdown', () => {
+ createComponent({
+ mountFn: mountExtended,
+ props: {
+ areScopedVariablesAvailable: false,
+ },
+ });
+
+ expect(findCiEnvironmentsDropdown().exists()).toBe(false);
+ expect(findEnvScopeInput().attributes('readonly')).toBe('readonly');
+ });
+ });
+ });
+
+ describe('Validations', () => {
+ const maskError = 'This variable can not be masked.';
+
+ describe('when the mask state is invalid', () => {
+ beforeEach(async () => {
+ const [variable] = mockVariablesWithScopes;
+ const invalidMaskVariable = {
+ ...variable,
+ value: 'd:;',
+ masked: false,
+ };
+ createComponent({
+ mountFn: mountExtended,
+ props: { selectedVariable: invalidMaskVariable },
+ });
+ trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+ await findMaskedVariableCheckbox().trigger('click');
+ });
+
+ it('disables the submit button', () => {
+ expect(findAddorUpdateButton().attributes('disabled')).toBeTruthy();
+ });
+
+ it('shows the correct error text', () => {
+ expect(findModal().text()).toContain(maskError);
+ });
+
+ it('sends the correct tracking event', () => {
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, EVENT_ACTION, {
+ label: EVENT_LABEL,
+ property: ';',
+ });
+ });
+ });
+
+ describe.each`
+ value | masked | eventSent | trackingErrorProperty
+ ${'secretValue'} | ${false} | ${0} | ${null}
+ ${'short'} | ${true} | ${0} | ${null}
+ ${'dollar$ign'} | ${false} | ${1} | ${'$'}
+ ${'dollar$ign'} | ${true} | ${1} | ${'$'}
+ ${'unsupported|char'} | ${true} | ${1} | ${'|'}
+ ${'unsupported|char'} | ${false} | ${0} | ${null}
+ `('Adding a new variable', ({ value, masked, eventSent, trackingErrorProperty }) => {
+ beforeEach(async () => {
+ const [variable] = mockVariablesWithScopes;
+ const invalidKeyVariable = {
+ ...variable,
+ value: '',
+ masked: false,
+ };
+ createComponent({
+ mountFn: mountExtended,
+ props: { selectedVariable: invalidKeyVariable },
+ });
+ trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+ await findValueField().vm.$emit('input', value);
+ if (masked) {
+ await findMaskedVariableCheckbox().trigger('click');
+ }
+ });
+
+ it(`${
+ eventSent > 0 ? 'sends the correct' : 'does not send the'
+ } variable validation tracking event with ${value}`, () => {
+ expect(trackingSpy).toHaveBeenCalledTimes(eventSent);
+
+ if (eventSent > 0) {
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, EVENT_ACTION, {
+ label: EVENT_LABEL,
+ property: trackingErrorProperty,
+ });
+ }
+ });
+ });
+
+ describe('when masked variable has acceptable value', () => {
+ beforeEach(() => {
+ const [variable] = mockVariablesWithScopes;
+ const validMaskandKeyVariable = {
+ ...variable,
+ key: AWS_ACCESS_KEY_ID,
+ value: '12345678',
+ masked: true,
+ };
+ createComponent({
+ mountFn: mountExtended,
+ props: { selectedVariable: validMaskandKeyVariable },
+ });
+ });
+
+ it('does not disable the submit button', () => {
+ expect(findAddorUpdateButton().attributes('disabled')).toBeFalsy();
+ });
+ });
+ });
+});
diff --git a/spec/frontend/ci_variable_list/mocks.js b/spec/frontend/ci_variable_list/mocks.js
new file mode 100644
index 00000000000..1ba50c74152
--- /dev/null
+++ b/spec/frontend/ci_variable_list/mocks.js
@@ -0,0 +1,116 @@
+import { variableTypes } from '~/ci_variable_list/constants';
+
+export const devName = 'dev';
+export const prodName = 'prod';
+
+export const mockVariables = [
+ {
+ __typename: 'CiVariable',
+ id: 1,
+ key: 'my-var',
+ masked: false,
+ protected: true,
+ value: 'env_val',
+ variableType: variableTypes.variableType,
+ },
+ {
+ __typename: 'CiVariable',
+ id: 2,
+ key: 'secret',
+ masked: true,
+ protected: false,
+ value: 'the_secret_value',
+ variableType: variableTypes.fileType,
+ },
+];
+
+export const mockVariablesWithScopes = mockVariables.map((variable) => {
+ return { ...variable, environmentScope: '*' };
+});
+
+const createDefaultVars = ({ withScope = true } = {}) => {
+ let base = mockVariables;
+
+ if (withScope) {
+ base = mockVariablesWithScopes;
+ }
+
+ return {
+ __typename: 'CiVariableConnection',
+ nodes: base,
+ };
+};
+
+const defaultEnvs = {
+ __typename: 'EnvironmentConnection',
+ nodes: [
+ {
+ __typename: 'Environment',
+ id: 1,
+ name: prodName,
+ },
+ {
+ __typename: 'Environment',
+ id: 2,
+ name: devName,
+ },
+ ],
+};
+
+export const mockEnvs = defaultEnvs.nodes;
+
+export const mockProjectEnvironments = {
+ data: {
+ project: {
+ __typename: 'Project',
+ id: 1,
+ environments: defaultEnvs,
+ },
+ },
+};
+
+export const mockProjectVariables = {
+ data: {
+ project: {
+ __typename: 'Project',
+ id: 1,
+ ciVariables: createDefaultVars(),
+ },
+ },
+};
+
+export const mockGroupEnvironments = {
+ data: {
+ group: {
+ __typename: 'Group',
+ id: 1,
+ environments: defaultEnvs,
+ },
+ },
+};
+
+export const mockGroupVariables = {
+ data: {
+ group: {
+ __typename: 'Group',
+ id: 1,
+ ciVariables: createDefaultVars(),
+ },
+ },
+};
+
+export const mockAdminVariables = {
+ data: {
+ ciVariables: createDefaultVars({ withScope: false }),
+ },
+};
+
+export const newVariable = {
+ id: 3,
+ environmentScope: 'new',
+ key: 'AWS_RANDOM_THING',
+ masked: true,
+ protected: false,
+ value: 'devops',
+ variableType: variableTypes.variableType,
+};
diff --git a/spec/frontend/ci_variable_list/utils_spec.js b/spec/frontend/ci_variable_list/utils_spec.js
new file mode 100644
index 00000000000..1676e786515
--- /dev/null
+++ b/spec/frontend/ci_variable_list/utils_spec.js
@@ -0,0 +1,68 @@
+import {
+ createJoinedEnvironments,
+ convertEnvironmentScope,
+ mapEnvironmentNames,
+} from '~/ci_variable_list/utils';
+import { allEnvironments } from '~/ci_variable_list/constants';
+
+describe('utils', () => {
+ const environments = ['dev', 'prod'];
+
+ describe('createJoinedEnvironments', () => {
+ it('returns only `environments` if `variables` argument is undefined', () => {
+ const variables = undefined;
+
+ expect(createJoinedEnvironments(variables, environments)).toEqual(environments);
+ });
+
+ it('returns a list of environments and environment scopes taken from variables in alphabetical order', () => {
+ const envScope1 = 'new1';
+ const envScope2 = 'new2';
+
+ const variables = [{ environmentScope: envScope1 }, { environmentScope: envScope2 }];
+
+ expect(createJoinedEnvironments(variables, environments)).toEqual([
+ environments[0],
+ envScope1,
+ envScope2,
+ environments[1],
+ ]);
+ });
+
+ it('removes duplicate environments', () => {
+ const envScope1 = environments[0];
+ const envScope2 = 'new2';
+
+ const variables = [{ environmentScope: envScope1 }, { environmentScope: envScope2 }];
+
+ expect(createJoinedEnvironments(variables, environments)).toEqual([
+ environments[0],
+ envScope2,
+ environments[1],
+ ]);
+ });
+ });
+
+ describe('convertEnvironmentScope', () => {
+ it('converts the * to the `All environments` text', () => {
+ expect(convertEnvironmentScope('*')).toBe(allEnvironments.text);
+ });
+
+ it('returns the environment as is if not the *', () => {
+ expect(convertEnvironmentScope('prod')).toBe('prod');
+ });
+ });
+
+ describe('mapEnvironmentNames', () => {
+ const envName = 'dev';
+ const envName2 = 'prod';
+
+ const nodes = [
+ { name: envName, otherProp: {} },
+ { name: envName2, otherProp: {} },
+ ];
+ it('flatten a nodes array with only their names', () => {
+ expect(mapEnvironmentNames(nodes)).toEqual([envName, envName2]);
+ });
+ });
+});
diff --git a/spec/frontend/content_editor/remark_markdown_processing_spec.js b/spec/frontend/content_editor/remark_markdown_processing_spec.js
index e1de2823726..ca552644258 100644
--- a/spec/frontend/content_editor/remark_markdown_processing_spec.js
+++ b/spec/frontend/content_editor/remark_markdown_processing_spec.js
@@ -1170,7 +1170,7 @@ _world_.
const trimmed = markdown.trim();
const document = await deserialize(trimmed);
- expect(expectedDoc).not.toBeFalsy();
+ expect(expectedDoc).not.toBe(false);
expect(document.toJSON()).toEqual(expectedDoc.toJSON());
expect(serialize(document)).toEqual(expectedMarkdown ?? trimmed);
},
diff --git a/spec/frontend/labels/labels_select_spec.js b/spec/frontend/labels/labels_select_spec.js
index f6e280564cc..63f7c725bc7 100644
--- a/spec/frontend/labels/labels_select_spec.js
+++ b/spec/frontend/labels/labels_select_spec.js
@@ -101,6 +101,12 @@ describe('LabelsSelect', () => {
expect($labelEl.find('a').attr('data-html')).toBe('true');
});
+ it('generated label item template has correct title for tooltip', () => {
+ expect($labelEl.find('a').attr('title')).toBe(
+ "<span class='font-weight-bold scoped-label-tooltip-title'>Scoped label</span><br>Foobar",
+ );
+ });
+
it('generated label item template has correct label styles and classes', () => {
expect($labelEl.find('span.gl-label-text').attr('style')).toBe(
`background-color: ${label.color};`,
diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js
index 312e4f636c3..2c6b603197d 100644
--- a/spec/frontend/lib/utils/url_utility_spec.js
+++ b/spec/frontend/lib/utils/url_utility_spec.js
@@ -348,15 +348,13 @@ describe('URL utility', () => {
describe('urlContainsSha', () => {
it('returns true when there is a valid 40-character SHA1 hash in the URL', () => {
shas.valid.forEach((sha) => {
- expect(
- urlUtils.urlContainsSha({ url: `http://urlstuff/${sha}/moreurlstuff` }),
- ).toBeTruthy();
+ expect(urlUtils.urlContainsSha({ url: `http://urlstuff/${sha}/moreurlstuff` })).toBe(true);
});
});
it('returns false when there is not a valid 40-character SHA1 hash in the URL', () => {
shas.invalid.forEach((str) => {
- expect(urlUtils.urlContainsSha({ url: `http://urlstuff/${str}/moreurlstuff` })).toBeFalsy();
+ expect(urlUtils.urlContainsSha({ url: `http://urlstuff/${str}/moreurlstuff` })).toBe(false);
});
});
});
@@ -813,13 +811,13 @@ describe('URL utility', () => {
});
it('should compare against the window location if no compare value is provided', () => {
- expect(urlUtils.urlIsDifferent('different')).toBeTruthy();
- expect(urlUtils.urlIsDifferent(current)).toBeFalsy();
+ expect(urlUtils.urlIsDifferent('different')).toBe(true);
+ expect(urlUtils.urlIsDifferent(current)).toBe(false);
});
it('should use the provided compare value', () => {
- expect(urlUtils.urlIsDifferent('different', current)).toBeTruthy();
- expect(urlUtils.urlIsDifferent(current, current)).toBeFalsy();
+ expect(urlUtils.urlIsDifferent('different', current)).toBe(true);
+ expect(urlUtils.urlIsDifferent(current, current)).toBe(false);
});
});
diff --git a/spec/frontend/projects/project_new_spec.js b/spec/frontend/projects/project_new_spec.js
index 3034037fb1d..4fcecc3a307 100644
--- a/spec/frontend/projects/project_new_spec.js
+++ b/spec/frontend/projects/project_new_spec.js
@@ -1,6 +1,7 @@
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { TEST_HOST } from 'helpers/test_constants';
import projectNew from '~/projects/project_new';
+import { mockTracking, triggerEvent, unmockTracking } from 'helpers/tracking_helper';
describe('New Project', () => {
let $projectImportUrl;
@@ -12,21 +13,27 @@ describe('New Project', () => {
beforeEach(() => {
setHTMLFixture(`
- <div class='toggle-import-form'>
- <div class='import-url-data'>
- <div class="form-group">
- <input id="project_import_url" />
- </div>
- <div id="import-url-auth-method">
- <div class="form-group">
- <input id="project-import-url-user" />
+ <div class="tab-pane active">
+ <div class='toggle-import-form'>
+ <form id="new_project">
+ <div class='import-url-data'>
+ <div class="form-group">
+ <input id="project_import_url" />
+ </div>
+ <div id="import-url-auth-method">
+ <div class="form-group">
+ <input id="project-import-url-user" />
+ </div>
+ <div class="form-group">
+ <input id="project_import_url_password" />
+ </div>
+ </div>
+ <input id="project_name" />
+ <input id="project_path" />
</div>
- <div class="form-group">
- <input id="project_import_url_password" />
- </div>
- </div>
- <input id="project_name" />
- <input id="project_path" />
+ <div class="js-user-readme-repo"></div>
+ <button class="js-create-project-button"/>
+ </form>
</div>
</div>
`);
@@ -45,6 +52,38 @@ describe('New Project', () => {
el.value = value;
};
+ describe('tracks manual path input', () => {
+ let trackingSpy;
+
+ beforeEach(() => {
+ trackingSpy = mockTracking('_category_', undefined, jest.spyOn);
+ projectNew.bindEvents();
+ $projectPath.oldInputValue = '_old_value_';
+ });
+
+ afterEach(() => {
+ unmockTracking();
+ });
+
+ it('tracks the event', () => {
+ $projectPath.value = '_new_value_';
+
+ triggerEvent($projectPath, 'blur');
+
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'user_input_path_slug', {
+ label: 'new_project_form',
+ });
+ });
+
+ it('does not track the event when there has been no change', () => {
+ $projectPath.value = '_old_value_';
+
+ triggerEvent($projectPath, 'blur');
+
+ expect(trackingSpy).not.toHaveBeenCalled();
+ });
+ });
+
describe('deriveProjectPathFromUrl', () => {
const dummyImportUrl = `${TEST_HOST}/dummy/import/url.git`;
diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_memory_usage_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_memory_usage_spec.js
index f0106914674..193a16bae8d 100644
--- a/spec/frontend/vue_merge_request_widget/components/mr_widget_memory_usage_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_memory_usage_spec.js
@@ -80,20 +80,20 @@ describe('MemoryUsage', () => {
it('should have default data', () => {
const data = MemoryUsage.data();
- expect(Array.isArray(data.memoryMetrics)).toBeTruthy();
+ expect(Array.isArray(data.memoryMetrics)).toBe(true);
expect(data.memoryMetrics.length).toBe(0);
expect(typeof data.deploymentTime).toBe('number');
expect(data.deploymentTime).toBe(0);
expect(typeof data.hasMetrics).toBe('boolean');
- expect(data.hasMetrics).toBeFalsy();
+ expect(data.hasMetrics).toBe(false);
expect(typeof data.loadFailed).toBe('boolean');
- expect(data.loadFailed).toBeFalsy();
+ expect(data.loadFailed).toBe(false);
expect(typeof data.loadingMetrics).toBe('boolean');
- expect(data.loadingMetrics).toBeTruthy();
+ expect(data.loadingMetrics).toBe(true);
expect(typeof data.backOffRequestCounter).toBe('number');
expect(data.backOffRequestCounter).toBe(0);
@@ -144,7 +144,7 @@ describe('MemoryUsage', () => {
vm.computeGraphData(metrics, deployment_time);
const { hasMetrics, memoryMetrics, deploymentTime, memoryFrom, memoryTo } = vm;
- expect(hasMetrics).toBeTruthy();
+ expect(hasMetrics).toBe(true);
expect(memoryMetrics.length).toBeGreaterThan(0);
expect(deploymentTime).toEqual(deployment_time);
expect(memoryFrom).toEqual('9.13');
@@ -171,7 +171,7 @@ describe('MemoryUsage', () => {
describe('template', () => {
it('should render template elements correctly', () => {
- expect(el.classList.contains('mr-memory-usage')).toBeTruthy();
+ expect(el.classList.contains('mr-memory-usage')).toBe(true);
expect(el.querySelector('.js-usage-info')).toBeDefined();
});
diff --git a/spec/lib/gitlab/github_import/importer/events/base_importer_spec.rb b/spec/lib/gitlab/github_import/importer/events/base_importer_spec.rb
index f113ffcd0a7..41fe5fbdbbd 100644
--- a/spec/lib/gitlab/github_import/importer/events/base_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/events/base_importer_spec.rb
@@ -4,10 +4,10 @@ require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::BaseImporter do
let(:project) { instance_double('Project') }
- let(:user_finder) { instance_double('Gitlab::GithubImport::UserFinder') }
+ let(:client) { instance_double('Gitlab::GithubImport::Client') }
let(:issue_event) { instance_double('Gitlab::GithubImport::Representation::IssueEvent') }
let(:importer_class) { Class.new(described_class) }
- let(:importer_instance) { importer_class.new(project, user_finder) }
+ let(:importer_instance) { importer_class.new(project, client) }
describe '#execute' do
it { expect { importer_instance.execute(issue_event) }.to raise_error(NotImplementedError) }
diff --git a/spec/lib/gitlab/github_import/importer/events/changed_assignee_spec.rb b/spec/lib/gitlab/github_import/importer/events/changed_assignee_spec.rb
index a1918dd0da8..2f6f727dc38 100644
--- a/spec/lib/gitlab/github_import/importer/events/changed_assignee_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/events/changed_assignee_spec.rb
@@ -3,14 +3,13 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedAssignee do
- subject(:importer) { described_class.new(project, user_finder) }
+ subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:assignee) { create(:user) }
let_it_be(:assigner) { create(:user) }
let(:client) { instance_double('Gitlab::GithubImport::Client') }
- let(:user_finder) { Gitlab::GithubImport::UserFinder.new(project, client) }
let(:issue) { create(:issue, project: project) }
let(:issue_event) do
@@ -22,7 +21,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedAssignee do
'created_at' => '2022-04-26 18:30:53 UTC',
'assigner' => { 'id' => assigner.id, 'login' => assigner.username },
'assignee' => { 'id' => assignee.id, 'login' => assignee.username },
- 'issue_db_id' => issue.id
+ 'issue' => { 'number' => issue.iid }
)
end
@@ -70,8 +69,13 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedAssignee do
describe '#execute' do
before do
- allow(user_finder).to receive(:find).with(assignee.id, assignee.username).and_return(assignee.id)
- allow(user_finder).to receive(:find).with(assigner.id, assigner.username).and_return(assigner.id)
+ allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
+ allow(finder).to receive(:database_id).and_return(issue.id)
+ end
+ allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
+ allow(finder).to receive(:find).with(assignee.id, assignee.username).and_return(assignee.id)
+ allow(finder).to receive(:find).with(assigner.id, assigner.username).and_return(assigner.id)
+ end
end
context 'when importing an assigned event' do
diff --git a/spec/lib/gitlab/github_import/importer/events/changed_label_spec.rb b/spec/lib/gitlab/github_import/importer/events/changed_label_spec.rb
index 98a8daf1653..e21672aa430 100644
--- a/spec/lib/gitlab/github_import/importer/events/changed_label_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/events/changed_label_spec.rb
@@ -3,13 +3,12 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedLabel do
- subject(:importer) { described_class.new(project, user_finder) }
+ subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:client) { instance_double('Gitlab::GithubImport::Client') }
- let(:user_finder) { Gitlab::GithubImport::UserFinder.new(project, client) }
let(:issue) { create(:issue, project: project) }
let!(:label) { create(:label, project: project) }
@@ -21,7 +20,8 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedLabel do
'commit_id' => nil,
'label_title' => label.title,
'issue_db_id' => issue.id,
- 'created_at' => '2022-04-26 18:30:53 UTC'
+ 'created_at' => '2022-04-26 18:30:53 UTC',
+ 'issue' => { 'number' => issue.iid }
)
end
@@ -45,7 +45,12 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedLabel do
before do
allow(Gitlab::Cache::Import::Caching).to receive(:read_integer).and_return(label.id)
- allow(user_finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
+ allow(finder).to receive(:database_id).and_return(issue.id)
+ end
+ allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
+ allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ end
end
context 'when importing a labeled event' do
diff --git a/spec/lib/gitlab/github_import/importer/events/changed_milestone_spec.rb b/spec/lib/gitlab/github_import/importer/events/changed_milestone_spec.rb
index a5852c967df..2687627fc23 100644
--- a/spec/lib/gitlab/github_import/importer/events/changed_milestone_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/events/changed_milestone_spec.rb
@@ -3,13 +3,12 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedMilestone do
- subject(:importer) { described_class.new(project, user_finder) }
+ subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:client) { instance_double('Gitlab::GithubImport::Client') }
- let(:user_finder) { Gitlab::GithubImport::UserFinder.new(project, client) }
let(:issue) { create(:issue, project: project) }
let!(:milestone) { create(:milestone, project: project) }
@@ -21,7 +20,8 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedMilestone do
'commit_id' => nil,
'milestone_title' => milestone.title,
'issue_db_id' => issue.id,
- 'created_at' => '2022-04-26 18:30:53 UTC'
+ 'created_at' => '2022-04-26 18:30:53 UTC',
+ 'issue' => { 'number' => issue.iid }
)
end
@@ -47,7 +47,12 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedMilestone do
describe '#execute' do
before do
allow(Gitlab::Cache::Import::Caching).to receive(:read_integer).and_return(milestone.id)
- allow(user_finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
+ allow(finder).to receive(:database_id).and_return(issue.id)
+ end
+ allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
+ allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ end
end
context 'when importing a milestoned event' do
diff --git a/spec/lib/gitlab/github_import/importer/events/closed_spec.rb b/spec/lib/gitlab/github_import/importer/events/closed_spec.rb
index 749c52a215e..9a49d80a8bb 100644
--- a/spec/lib/gitlab/github_import/importer/events/closed_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/events/closed_spec.rb
@@ -3,13 +3,12 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::Closed do
- subject(:importer) { described_class.new(project, user_finder) }
+ subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:client) { instance_double('Gitlab::GithubImport::Client') }
- let(:user_finder) { Gitlab::GithubImport::UserFinder.new(project, client) }
let(:issue) { create(:issue, project: project) }
let(:commit_id) { nil }
@@ -22,7 +21,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Closed do
'event' => 'closed',
'created_at' => '2022-04-26 18:30:53 UTC',
'commit_id' => commit_id,
- 'issue_db_id' => issue.id
+ 'issue' => { 'number' => issue.iid }
)
end
@@ -48,7 +47,12 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Closed do
end
before do
- allow(user_finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
+ allow(finder).to receive(:database_id).and_return(issue.id)
+ end
+ allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
+ allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ end
end
it 'creates expected event and state event' do
diff --git a/spec/lib/gitlab/github_import/importer/events/cross_referenced_spec.rb b/spec/lib/gitlab/github_import/importer/events/cross_referenced_spec.rb
index fd2e564d1b1..68e001c7364 100644
--- a/spec/lib/gitlab/github_import/importer/events/cross_referenced_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/events/cross_referenced_spec.rb
@@ -3,14 +3,12 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::CrossReferenced, :clean_gitlab_redis_cache do
- subject(:importer) { described_class.new(project, user_finder) }
+ subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
- let(:sawyer_stub) { Struct.new(:iid, :issuable_type, keyword_init: true) }
let(:client) { instance_double('Gitlab::GithubImport::Client') }
- let(:user_finder) { Gitlab::GithubImport::UserFinder.new(project, client) }
let(:issue_iid) { 999 }
let(:issue) { create(:issue, project: project, iid: issue_iid) }
@@ -32,7 +30,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::CrossReferenced, :clean_g
}
},
'created_at' => '2022-04-26 18:30:53 UTC',
- 'issue_db_id' => issue.id
+ 'issue' => { 'number' => issue.iid }
)
end
@@ -41,7 +39,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::CrossReferenced, :clean_g
{
system: true,
noteable_type: Issue.name,
- noteable_id: issue_event.issue_db_id,
+ noteable_id: issue.id,
project_id: project.id,
author_id: user.id,
note: expected_note_body,
@@ -53,10 +51,13 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::CrossReferenced, :clean_g
let(:expected_note_body) { "mentioned in issue ##{referenced_in.iid}" }
before do
- other_issue_resource = sawyer_stub.new(iid: referenced_in.iid, issuable_type: 'Issue')
- Gitlab::GithubImport::IssuableFinder.new(project, other_issue_resource)
- .cache_database_id(referenced_in.iid)
- allow(user_finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
+ allow(finder).to receive(:database_id).and_return(referenced_in.iid)
+ allow(finder).to receive(:database_id).and_return(issue.id)
+ end
+ allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
+ allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ end
end
it 'creates expected note' do
@@ -75,11 +76,13 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::CrossReferenced, :clean_g
let(:expected_note_body) { "mentioned in merge request !#{referenced_in.iid}" }
before do
- other_issue_resource =
- sawyer_stub.new(iid: referenced_in.iid, issuable_type: 'MergeRequest')
- Gitlab::GithubImport::IssuableFinder.new(project, other_issue_resource)
- .cache_database_id(referenced_in.iid)
- allow(user_finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
+ allow(finder).to receive(:database_id).and_return(referenced_in.iid)
+ allow(finder).to receive(:database_id).and_return(issue.id)
+ end
+ allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
+ allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ end
end
it 'creates expected note' do
diff --git a/spec/lib/gitlab/github_import/importer/events/renamed_spec.rb b/spec/lib/gitlab/github_import/importer/events/renamed_spec.rb
index 8acf82af40c..316ea798965 100644
--- a/spec/lib/gitlab/github_import/importer/events/renamed_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/events/renamed_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::Renamed do
- subject(:importer) { described_class.new(project, user_finder) }
+ subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
@@ -11,7 +11,6 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Renamed do
let(:issue) { create(:issue, project: project) }
let(:client) { instance_double('Gitlab::GithubImport::Client') }
- let(:user_finder) { Gitlab::GithubImport::UserFinder.new(project, client) }
let(:issue_event) do
Gitlab::GithubImport::Representation::IssueEvent.from_json_hash(
'id' => 6501124486,
@@ -21,7 +20,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Renamed do
'created_at' => '2022-04-26 18:30:53 UTC',
'old_title' => 'old title',
'new_title' => 'new title',
- 'issue_db_id' => issue.id
+ 'issue' => { 'number' => issue.iid }
)
end
@@ -48,7 +47,12 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Renamed do
describe '#execute' do
before do
- allow(user_finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
+ allow(finder).to receive(:database_id).and_return(issue.id)
+ end
+ allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
+ allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ end
end
it 'creates expected note' do
diff --git a/spec/lib/gitlab/github_import/importer/events/reopened_spec.rb b/spec/lib/gitlab/github_import/importer/events/reopened_spec.rb
index 39b8809dfa4..2461dbb9701 100644
--- a/spec/lib/gitlab/github_import/importer/events/reopened_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/events/reopened_spec.rb
@@ -3,13 +3,12 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::Reopened, :aggregate_failures do
- subject(:importer) { described_class.new(project, user_finder) }
+ subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:client) { instance_double('Gitlab::GithubImport::Client') }
- let(:user_finder) { Gitlab::GithubImport::UserFinder.new(project, client) }
let(:issue) { create(:issue, project: project) }
let(:issue_event) do
@@ -20,7 +19,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Reopened, :aggregate_fail
'actor' => { 'id' => user.id, 'login' => user.username },
'event' => 'reopened',
'created_at' => '2022-04-26 18:30:53 UTC',
- 'issue_db_id' => issue.id
+ 'issue' => { 'number' => issue.iid }
)
end
@@ -45,7 +44,12 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Reopened, :aggregate_fail
end
before do
- allow(user_finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
+ allow(finder).to receive(:database_id).and_return(issue.id)
+ end
+ allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
+ allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ end
end
it 'creates expected event and state event' do
diff --git a/spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb b/spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb
index fee7c2708a4..33d5fbf13a0 100644
--- a/spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb
@@ -33,7 +33,7 @@ RSpec.describe Gitlab::GithubImport::Importer::IssueEventImporter, :clean_gitlab
specific_importer = double(importer_class.name) # rubocop:disable RSpec/VerifiedDoubles
expect(importer_class)
- .to receive(:new).with(project, anything)
+ .to receive(:new).with(project, client)
.and_return(specific_importer)
expect(specific_importer).to receive(:execute).with(issue_event)
diff --git a/spec/lib/gitlab/github_import/importer/issue_events_importer_spec.rb b/spec/lib/gitlab/github_import/importer/issue_events_importer_spec.rb
new file mode 100644
index 00000000000..8d4c1b01e50
--- /dev/null
+++ b/spec/lib/gitlab/github_import/importer/issue_events_importer_spec.rb
@@ -0,0 +1,122 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::Importer::IssueEventsImporter do
+ subject(:importer) { described_class.new(project, client, parallel: parallel) }
+
+ let(:project) { instance_double(Project, id: 4, import_source: 'foo/bar') }
+ let(:client) { instance_double(Gitlab::GithubImport::Client) }
+
+ let(:parallel) { true }
+ let(:issue_event) do
+ struct = Struct.new(
+ :id, :node_id, :url, :actor, :event, :commit_id, :commit_url, :label, :rename, :milestone,
+ :source, :assignee, :assigner, :issue, :created_at, :performed_via_github_app,
+ keyword_init: true
+ )
+ struct.new(id: rand(10), event: 'closed', created_at: '2022-04-26 18:30:53 UTC')
+ end
+
+ describe '#parallel?' do
+ context 'when running in parallel mode' do
+ it { expect(importer).to be_parallel }
+ end
+
+ context 'when running in sequential mode' do
+ let(:parallel) { false }
+
+ it { expect(importer).not_to be_parallel }
+ end
+ end
+
+ describe '#execute' do
+ context 'when running in parallel mode' do
+ it 'imports events in parallel' do
+ expect(importer).to receive(:parallel_import)
+
+ importer.execute
+ end
+ end
+
+ context 'when running in sequential mode' do
+ let(:parallel) { false }
+
+ it 'imports notes in sequence' do
+ expect(importer).to receive(:sequential_import)
+
+ importer.execute
+ end
+ end
+ end
+
+ describe '#sequential_import' do
+ let(:parallel) { false }
+
+ it 'imports each event in sequence' do
+ event_importer = instance_double(Gitlab::GithubImport::Importer::IssueEventImporter)
+
+ allow(importer).to receive(:each_object_to_import).and_yield(issue_event)
+
+ expect(Gitlab::GithubImport::Importer::IssueEventImporter)
+ .to receive(:new)
+ .with(
+ an_instance_of(Gitlab::GithubImport::Representation::IssueEvent),
+ project,
+ client
+ )
+ .and_return(event_importer)
+
+ expect(event_importer).to receive(:execute)
+
+ importer.sequential_import
+ end
+ end
+
+ describe '#parallel_import' do
+ it 'imports each note in parallel' do
+ allow(importer).to receive(:each_object_to_import).and_yield(issue_event)
+
+ expect(Gitlab::GithubImport::ImportIssueEventWorker).to receive(:bulk_perform_in).with(
+ 1.second, [
+ [project.id, an_instance_of(Hash), an_instance_of(String)]
+ ], batch_size: 1000, batch_delay: 1.minute
+ )
+
+ waiter = importer.parallel_import
+
+ expect(waiter).to be_an_instance_of(Gitlab::JobWaiter)
+ expect(waiter.jobs_remaining).to eq(1)
+ end
+ end
+
+ describe '#importer_class' do
+ it { expect(importer.importer_class).to eq Gitlab::GithubImport::Importer::IssueEventImporter }
+ end
+
+ describe '#representation_class' do
+ it { expect(importer.representation_class).to eq Gitlab::GithubImport::Representation::IssueEvent }
+ end
+
+ describe '#sidekiq_worker_class' do
+ it { expect(importer.sidekiq_worker_class).to eq Gitlab::GithubImport::ImportIssueEventWorker }
+ end
+
+ describe '#object_type' do
+ it { expect(importer.object_type).to eq :issue_event }
+ end
+
+ describe '#collection_method' do
+ it { expect(importer.collection_method).to eq :repository_issue_events }
+ end
+
+ describe '#id_for_already_imported_cache' do
+ it 'returns the ID of the given note' do
+ expect(importer.id_for_already_imported_cache(issue_event)).to eq(issue_event.id)
+ end
+ end
+
+ describe '#collection_options' do
+ it { expect(importer.collection_options).to eq({}) }
+ end
+end
diff --git a/spec/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer_spec.rb b/spec/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer_spec.rb
index 087faeffe02..bb1ee79ad93 100644
--- a/spec/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer_spec.rb
@@ -53,7 +53,7 @@ RSpec.describe Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter
describe '#each_object_to_import', :clean_gitlab_redis_cache do
let(:issue_event) do
- struct = Struct.new(:id, :event, :created_at, :issue_db_id, keyword_init: true)
+ struct = Struct.new(:id, :event, :created_at, :issue, keyword_init: true)
struct.new(id: rand(10), event: 'closed', created_at: '2022-04-26 18:30:53 UTC')
end
@@ -81,7 +81,7 @@ RSpec.describe Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter
counter = 0
subject.each_object_to_import do |object|
expect(object).to eq issue_event
- expect(issue_event.issue_db_id).to eq issue.id
+ expect(issue_event.issue['number']).to eq issue.iid
counter += 1
end
expect(counter).to eq 1
diff --git a/spec/lib/gitlab/github_import/issuable_finder_spec.rb b/spec/lib/gitlab/github_import/issuable_finder_spec.rb
index 3afd006109b..d550f15e8c5 100644
--- a/spec/lib/gitlab/github_import/issuable_finder_spec.rb
+++ b/spec/lib/gitlab/github_import/issuable_finder_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::GithubImport::IssuableFinder, :clean_gitlab_redis_cache do
let(:project) { double(:project, id: 4, group: nil) }
let(:issue) do
- double(:issue, issuable_type: MergeRequest, iid: 1)
+ double(:issue, issuable_type: MergeRequest, issuable_id: 1)
end
let(:finder) { described_class.new(project, issue) }
diff --git a/spec/lib/gitlab/github_import/representation/issue_event_spec.rb b/spec/lib/gitlab/github_import/representation/issue_event_spec.rb
index cf796b55b14..d3a98035e73 100644
--- a/spec/lib/gitlab/github_import/representation/issue_event_spec.rb
+++ b/spec/lib/gitlab/github_import/representation/issue_event_spec.rb
@@ -25,8 +25,8 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
expect(issue_event.source).to eq({ type: 'issue', id: 123456 })
end
- it 'includes the issue_db_id' do
- expect(issue_event.issue_db_id).to eq(100500)
+ it 'includes the issue data' do
+ expect(issue_event.issue).to eq({ number: 2, pull_request: pull_request })
end
context 'when actor data present' do
@@ -119,6 +119,24 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
end
end
+ describe '#issuable_id' do
+ it 'returns issuable_id' do
+ expect(issue_event.issuable_id).to eq(2)
+ end
+ end
+
+ describe '#issuable_type' do
+ context 'when event related to issue' do
+ it { expect(issue_event.issuable_type).to eq('Issue') }
+ end
+
+ context 'when event related to pull request' do
+ let(:pull_request) { { url: FFaker::Internet.http_url } }
+
+ it { expect(issue_event.issuable_type).to eq('MergeRequest') }
+ end
+ end
+
describe '#github_identifiers' do
it 'returns a hash with needed identifiers' do
expect(issue_event.github_identifiers).to eq({ id: 6501124486 })
@@ -130,7 +148,7 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
let(:response) do
event_resource = Struct.new(
:id, :node_id, :url, :actor, :event, :commit_id, :commit_url, :label, :rename, :milestone,
- :source, :assignee, :assigner, :issue_db_id, :created_at, :performed_via_github_app,
+ :source, :assignee, :assigner, :issue, :created_at, :performed_via_github_app,
keyword_init: true
)
user_resource = Struct.new(:id, :login, keyword_init: true)
@@ -149,7 +167,7 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
source: { type: 'issue', id: 123456 },
assignee: with_assignee ? user_resource.new(id: 5, login: 'tom') : nil,
assigner: with_assignee ? user_resource.new(id: 6, login: 'jerry') : nil,
- issue_db_id: 100500,
+ issue: { 'number' => 2, 'pull_request' => pull_request },
created_at: '2022-04-26 18:30:53 UTC',
performed_via_github_app: nil
)
@@ -160,6 +178,7 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
let(:with_rename) { true }
let(:with_milestone) { true }
let(:with_assignee) { true }
+ let(:pull_request) { nil }
it_behaves_like 'an IssueEvent' do
let(:issue_event) { described_class.from_api_response(response) }
@@ -185,7 +204,7 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
'source' => { 'type' => 'issue', 'id' => 123456 },
'assignee' => (with_assignee ? { 'id' => 5, 'login' => 'tom' } : nil),
'assigner' => (with_assignee ? { 'id' => 6, 'login' => 'jerry' } : nil),
- "issue_db_id" => 100500,
+ 'issue' => { 'number' => 2, 'pull_request' => pull_request },
'created_at' => '2022-04-26 18:30:53 UTC',
'performed_via_github_app' => nil
}
@@ -196,6 +215,7 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
let(:with_rename) { true }
let(:with_milestone) { true }
let(:with_assignee) { true }
+ let(:pull_request) { nil }
let(:issue_event) { described_class.from_json_hash(hash) }
end
diff --git a/spec/support/shared_examples/controllers/search_cross_project_authorization_shared_examples.rb b/spec/support/shared_examples/controllers/search_cross_project_authorization_shared_examples.rb
new file mode 100644
index 00000000000..9421561aea4
--- /dev/null
+++ b/spec/support/shared_examples/controllers/search_cross_project_authorization_shared_examples.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples_for 'when the user cannot read cross project' do |action, params|
+ before do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?).with(user, :read_cross_project, :global).and_return(false)
+ end
+
+ it 'blocks access without a project_id' do
+ get action, params: params
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'allows access with a project_id' do
+ get action, params: params.merge(project_id: create(:project, :public).id)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+end
diff --git a/spec/support/shared_examples/controllers/search_external_authorization_service_shared_examples.rb b/spec/support/shared_examples/controllers/search_external_authorization_service_shared_examples.rb
new file mode 100644
index 00000000000..6b72988b3e6
--- /dev/null
+++ b/spec/support/shared_examples/controllers/search_external_authorization_service_shared_examples.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples_for 'with external authorization service enabled' do |action, params|
+ include ExternalAuthorizationServiceHelpers
+
+ let(:project) { create(:project, namespace: user.namespace) }
+ let(:note) { create(:note_on_issue, project: project) }
+
+ before do
+ enable_external_authorization_service_check
+ end
+
+ it 'renders a 403 when no project is given' do
+ get action, params: params
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'renders a 200 when a project was set' do
+ get action, params: params.merge(project_id: project.id)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+end
diff --git a/spec/workers/gitlab/github_import/stage/import_issue_events_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_issue_events_worker_spec.rb
index b3c6a48767c..932152c0764 100644
--- a/spec/workers/gitlab/github_import/stage/import_issue_events_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_issue_events_worker_spec.rb
@@ -8,37 +8,66 @@ RSpec.describe Gitlab::GithubImport::Stage::ImportIssueEventsWorker do
let(:project) { create(:project) }
let!(:group) { create(:group, projects: [project]) }
let(:feature_flag_state) { [group] }
+ let(:single_endpoint_feature_flag_state) { [group] }
describe '#import' do
let(:importer) { instance_double('Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter') }
let(:client) { instance_double('Gitlab::GithubImport::Client') }
before do
+ stub_feature_flags(github_importer_single_endpoint_issue_events_import: single_endpoint_feature_flag_state)
stub_feature_flags(github_importer_issue_events_import: feature_flag_state)
end
- it 'imports all the issue events' do
- waiter = Gitlab::JobWaiter.new(2, '123')
+ context 'when single endpoint feature flag enabled' do
+ it 'imports all the issue events' do
+ waiter = Gitlab::JobWaiter.new(2, '123')
- expect(Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter)
- .to receive(:new)
- .with(project, client)
- .and_return(importer)
+ expect(Gitlab::GithubImport::Importer::IssueEventsImporter).not_to receive(:new)
+ expect(Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter)
+ .to receive(:new)
+ .with(project, client)
+ .and_return(importer)
- expect(importer).to receive(:execute).and_return(waiter)
+ expect(importer).to receive(:execute).and_return(waiter)
- expect(Gitlab::GithubImport::AdvanceStageWorker)
- .to receive(:perform_async)
- .with(project.id, { '123' => 2 }, :notes)
+ expect(Gitlab::GithubImport::AdvanceStageWorker)
+ .to receive(:perform_async)
+ .with(project.id, { '123' => 2 }, :notes)
- worker.import(client, project)
+ worker.import(client, project)
+ end
+ end
+
+ context 'when import issue events feature flag enabled' do
+ let(:single_endpoint_feature_flag_state) { false }
+
+ it 'imports the issue events partly' do
+ waiter = Gitlab::JobWaiter.new(2, '123')
+
+ expect(Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter).not_to receive(:new)
+ expect(Gitlab::GithubImport::Importer::IssueEventsImporter)
+ .to receive(:new)
+ .with(project, client)
+ .and_return(importer)
+
+ expect(importer).to receive(:execute).and_return(waiter)
+
+ expect(Gitlab::GithubImport::AdvanceStageWorker)
+ .to receive(:perform_async)
+ .with(project.id, { '123' => 2 }, :notes)
+
+ worker.import(client, project)
+ end
end
- context 'when feature flag is disabled' do
+ context 'when feature flags are disabled' do
let(:feature_flag_state) { false }
+ let(:single_endpoint_feature_flag_state) { false }
it 'skips issue events import and calls next stage' do
expect(Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter).not_to receive(:new)
+ expect(Gitlab::GithubImport::Importer::IssueEventsImporter).not_to receive(:new)
expect(Gitlab::GithubImport::AdvanceStageWorker).to receive(:perform_async).with(project.id, {}, :notes)
worker.import(client, project)