Commit 76e89467 authored by Jannik Lehmann's avatar Jannik Lehmann Committed by Mark Florian

Refactor manage_via_mr Component

This commit solves 326249.
The security Configuration Table for CE and EE has been refactored.
Both of those tables support the ability to enable scanners via Merge Request.
Previously this was handled separately leading to duplicate code.
parent 021ca831
<script> <script>
import { GlLink, GlTable, GlAlert } from '@gitlab/ui'; import { GlLink, GlTable, GlAlert } from '@gitlab/ui';
import { s__, sprintf } from '~/locale'; import { s__, sprintf } from '~/locale';
import ManageViaMR from '~/vue_shared/security_configuration/components/manage_via_mr.vue';
import { import {
REPORT_TYPE_SAST, REPORT_TYPE_SAST,
REPORT_TYPE_DAST, REPORT_TYPE_DAST,
...@@ -11,8 +12,8 @@ import { ...@@ -11,8 +12,8 @@ import {
REPORT_TYPE_API_FUZZING, REPORT_TYPE_API_FUZZING,
REPORT_TYPE_LICENSE_COMPLIANCE, REPORT_TYPE_LICENSE_COMPLIANCE,
} from '~/vue_shared/security_reports/constants'; } from '~/vue_shared/security_reports/constants';
import ManageSast from './manage_sast.vue';
import { scanners } from './scanners_constants'; import { scanners } from './constants';
import Upgrade from './upgrade.vue'; import Upgrade from './upgrade.vue';
const borderClasses = 'gl-border-b-1! gl-border-b-solid! gl-border-gray-100!'; const borderClasses = 'gl-border-b-1! gl-border-b-solid! gl-border-gray-100!';
...@@ -40,7 +41,7 @@ export default { ...@@ -40,7 +41,7 @@ export default {
}, },
getComponentForItem(item) { getComponentForItem(item) {
const COMPONENTS = { const COMPONENTS = {
[REPORT_TYPE_SAST]: ManageSast, [REPORT_TYPE_SAST]: ManageViaMR,
[REPORT_TYPE_DAST]: Upgrade, [REPORT_TYPE_DAST]: Upgrade,
[REPORT_TYPE_DAST_PROFILES]: Upgrade, [REPORT_TYPE_DAST_PROFILES]: Upgrade,
[REPORT_TYPE_DEPENDENCY_SCANNING]: Upgrade, [REPORT_TYPE_DEPENDENCY_SCANNING]: Upgrade,
...@@ -49,7 +50,6 @@ export default { ...@@ -49,7 +50,6 @@ export default {
[REPORT_TYPE_API_FUZZING]: Upgrade, [REPORT_TYPE_API_FUZZING]: Upgrade,
[REPORT_TYPE_LICENSE_COMPLIANCE]: Upgrade, [REPORT_TYPE_LICENSE_COMPLIANCE]: Upgrade,
}; };
return COMPONENTS[item.type]; return COMPONENTS[item.type];
}, },
}, },
...@@ -95,7 +95,12 @@ export default { ...@@ -95,7 +95,12 @@ export default {
</template> </template>
<template #cell(manage)="{ item }"> <template #cell(manage)="{ item }">
<component :is="getComponentForItem(item)" :data-testid="item.type" @error="onError" /> <component
:is="getComponentForItem(item)"
:feature="item"
:data-testid="item.type"
@error="onError"
/>
</template> </template>
</gl-table> </gl-table>
</div> </div>
......
import { helpPagePath } from '~/helpers/help_page_helper'; import { helpPagePath } from '~/helpers/help_page_helper';
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
import configureSastMutation from '~/security_configuration/graphql/configure_sast.mutation.graphql';
import { import {
REPORT_TYPE_SAST, REPORT_TYPE_SAST,
REPORT_TYPE_DAST, REPORT_TYPE_DAST,
...@@ -134,3 +135,18 @@ export const scanners = [ ...@@ -134,3 +135,18 @@ export const scanners = [
type: REPORT_TYPE_LICENSE_COMPLIANCE, type: REPORT_TYPE_LICENSE_COMPLIANCE,
}, },
]; ];
export const featureToMutationMap = {
[REPORT_TYPE_SAST]: {
mutationId: 'configureSast',
getMutationPayload: (projectPath) => ({
mutation: configureSastMutation,
variables: {
input: {
projectPath,
configuration: { global: [], pipeline: [], analyzers: [] },
},
},
}),
},
};
<script>
import { GlButton } from '@gitlab/ui';
import { redirectTo } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
import configureSastMutation from '~/security_configuration/graphql/configure_sast.mutation.graphql';
export default {
components: {
GlButton,
},
inject: {
projectPath: {
from: 'projectPath',
default: '',
},
},
data() {
return {
isLoading: false,
};
},
methods: {
async mutate() {
this.isLoading = true;
try {
const { data } = await this.$apollo.mutate({
mutation: configureSastMutation,
variables: {
input: {
projectPath: this.projectPath,
configuration: { global: [], pipeline: [], analyzers: [] },
},
},
});
const { errors, successPath } = data.configureSast;
if (errors.length > 0) {
throw new Error(errors[0]);
}
if (!successPath) {
throw new Error(s__('SecurityConfiguration|SAST merge request creation mutation failed'));
}
redirectTo(successPath);
} catch (e) {
this.$emit('error', e.message);
this.isLoading = false;
}
},
},
};
</script>
<template>
<gl-button :loading="isLoading" variant="success" category="secondary" @click="mutate">{{
s__('SecurityConfiguration|Configure via merge request')
}}</gl-button>
</template>
<script> <script>
import { GlLink, GlSprintf } from '@gitlab/ui'; import { GlLink, GlSprintf } from '@gitlab/ui';
import { UPGRADE_CTA } from './scanners_constants'; import { UPGRADE_CTA } from './constants';
export default { export default {
components: { components: {
......
<script> <script>
import { GlButton } from '@gitlab/ui'; import { GlButton } from '@gitlab/ui';
import { featureToMutationMap } from 'ee_else_ce/security_configuration/components/constants';
import { redirectTo } from '~/lib/utils/url_utility'; import { redirectTo } from '~/lib/utils/url_utility';
import { sprintf, s__ } from '~/locale'; import { sprintf, s__ } from '~/locale';
import apolloProvider from '../graphql/provider'; import apolloProvider from '../provider';
import { featureToMutationMap } from './constants';
export default { export default {
apolloProvider, apolloProvider,
components: { components: {
GlButton, GlButton,
}, },
inject: { inject: ['projectPath'],
projectPath: {
from: 'projectPath',
default: '',
},
},
props: { props: {
feature: { feature: {
type: Object, type: Object,
...@@ -36,15 +31,9 @@ export default { ...@@ -36,15 +31,9 @@ export default {
async mutate() { async mutate() {
this.isLoading = true; this.isLoading = true;
try { try {
const { data } = await this.$apollo.mutate({ const mutation = this.featureSettings;
mutation: this.featureSettings.mutation, const { data } = await this.$apollo.mutate(mutation.getMutationPayload(this.projectPath));
variables: { const { errors, successPath } = data[mutation.mutationId];
input: {
projectPath: this.projectPath,
},
},
});
const { errors, successPath } = data[this.featureSettings.type];
if (errors.length > 0) { if (errors.length > 0) {
throw new Error(errors[0]); throw new Error(errors[0]);
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui'; import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui';
import { parseBoolean } from '~/lib/utils/common_utils'; import { parseBoolean } from '~/lib/utils/common_utils';
import { s__, __ } from '~/locale'; import { s__, __ } from '~/locale';
import { scanners } from '~/security_configuration/components/scanners_constants'; import { scanners } from '~/security_configuration/components/constants';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import AutoFixSettings from './auto_fix_settings.vue'; import AutoFixSettings from './auto_fix_settings.vue';
......
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { featureToMutationMap as featureToMutationMapCE } from '~/security_configuration/components/constants';
import { import {
REPORT_TYPE_DEPENDENCY_SCANNING, REPORT_TYPE_DEPENDENCY_SCANNING,
REPORT_TYPE_SECRET_DETECTION, REPORT_TYPE_SECRET_DETECTION,
...@@ -23,12 +24,27 @@ export const CUSTOM_VALUE_MESSAGE = s__( ...@@ -23,12 +24,27 @@ export const CUSTOM_VALUE_MESSAGE = s__(
); );
export const featureToMutationMap = { export const featureToMutationMap = {
...featureToMutationMapCE,
[REPORT_TYPE_DEPENDENCY_SCANNING]: { [REPORT_TYPE_DEPENDENCY_SCANNING]: {
type: 'configureDependencyScanning', mutationId: 'configureDependencyScanning',
mutation: configureDependencyScanningMutation, getMutationPayload: (projectPath) => ({
mutation: configureDependencyScanningMutation,
variables: {
input: {
projectPath,
},
},
}),
}, },
[REPORT_TYPE_SECRET_DETECTION]: { [REPORT_TYPE_SECRET_DETECTION]: {
type: 'configureSecretDetection', mutationId: 'configureSecretDetection',
mutation: configureSecretDetectionMutation, getMutationPayload: (projectPath) => ({
mutation: configureSecretDetectionMutation,
variables: {
input: {
projectPath,
},
},
}),
}, },
}; };
<script> <script>
import { propsUnion } from '~/vue_shared/components/lib/utils/props_utils'; import { propsUnion } from '~/vue_shared/components/lib/utils/props_utils';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import ManageViaMr from '~/vue_shared/security_configuration/components/manage_via_mr.vue';
import { import {
REPORT_TYPE_DAST_PROFILES, REPORT_TYPE_DAST_PROFILES,
REPORT_TYPE_DEPENDENCY_SCANNING, REPORT_TYPE_DEPENDENCY_SCANNING,
...@@ -8,7 +9,6 @@ import { ...@@ -8,7 +9,6 @@ import {
} from '~/vue_shared/security_reports/constants'; } from '~/vue_shared/security_reports/constants';
import ManageDastProfiles from './manage_dast_profiles.vue'; import ManageDastProfiles from './manage_dast_profiles.vue';
import ManageGeneric from './manage_generic.vue'; import ManageGeneric from './manage_generic.vue';
import ManageViaMr from './manage_via_mr.vue';
const scannerComponentMap = { const scannerComponentMap = {
[REPORT_TYPE_DAST_PROFILES]: ManageDastProfiles, [REPORT_TYPE_DAST_PROFILES]: ManageDastProfiles,
......
...@@ -18,8 +18,8 @@ export const initSecurityConfiguration = (el) => { ...@@ -18,8 +18,8 @@ export const initSecurityConfiguration = (el) => {
containerScanningHelpPath, containerScanningHelpPath,
dependencyScanningHelpPath, dependencyScanningHelpPath,
toggleAutofixSettingEndpoint, toggleAutofixSettingEndpoint,
gitlabCiHistoryPath,
projectPath, projectPath,
gitlabCiHistoryPath,
} = el.dataset; } = el.dataset;
return new Vue({ return new Vue({
......
...@@ -5,7 +5,7 @@ import SecurityConfigurationApp from 'ee/security_configuration/components/app.v ...@@ -5,7 +5,7 @@ import SecurityConfigurationApp from 'ee/security_configuration/components/app.v
import ConfigurationTable from 'ee/security_configuration/components/configuration_table.vue'; import ConfigurationTable from 'ee/security_configuration/components/configuration_table.vue';
import { useLocalStorageSpy } from 'helpers/local_storage_helper'; import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import stubChildren from 'helpers/stub_children'; import stubChildren from 'helpers/stub_children';
import { scanners } from '~/security_configuration/components/scanners_constants'; import { scanners } from '~/security_configuration/components/constants';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import { generateFeatures } from './helpers'; import { generateFeatures } from './helpers';
......
import { scanners } from '~/security_configuration/components/scanners_constants'; import { scanners } from '~/security_configuration/components/constants';
export const generateFeatures = (n, overrides = {}) => { export const generateFeatures = (n, overrides = {}) => {
return [...Array(n).keys()].map((i) => ({ return [...Array(n).keys()].map((i) => ({
......
...@@ -2,7 +2,7 @@ import { shallowMount } from '@vue/test-utils'; ...@@ -2,7 +2,7 @@ import { shallowMount } from '@vue/test-utils';
import ManageDastProfiles from 'ee/security_configuration/components/manage_dast_profiles.vue'; import ManageDastProfiles from 'ee/security_configuration/components/manage_dast_profiles.vue';
import ManageFeature from 'ee/security_configuration/components/manage_feature.vue'; import ManageFeature from 'ee/security_configuration/components/manage_feature.vue';
import ManageGeneric from 'ee/security_configuration/components/manage_generic.vue'; import ManageGeneric from 'ee/security_configuration/components/manage_generic.vue';
import ManageViaMr from 'ee/security_configuration/components/manage_via_mr.vue'; import ManageViaMr from '~/vue_shared/security_configuration/components/manage_via_mr.vue';
import { import {
REPORT_TYPE_DAST_PROFILES, REPORT_TYPE_DAST_PROFILES,
REPORT_TYPE_DEPENDENCY_SCANNING, REPORT_TYPE_DEPENDENCY_SCANNING,
......
...@@ -10,9 +10,7 @@ import { redirectTo } from '~/lib/utils/url_utility'; ...@@ -10,9 +10,7 @@ import { redirectTo } from '~/lib/utils/url_utility';
import configureSastMutation from '~/security_configuration/graphql/configure_sast.mutation.graphql'; import configureSastMutation from '~/security_configuration/graphql/configure_sast.mutation.graphql';
import { makeEntities, makeSastCiConfiguration } from '../../helpers'; import { makeEntities, makeSastCiConfiguration } from '../../helpers';
jest.mock('~/lib/utils/url_utility', () => ({ jest.mock('~/lib/utils/url_utility');
redirectTo: jest.fn(),
}));
const projectPath = 'group/project'; const projectPath = 'group/project';
const sastAnalyzersDocumentationPath = '/help/sast/analyzers'; const sastAnalyzersDocumentationPath = '/help/sast/analyzers';
......
...@@ -2,18 +2,18 @@ import { GlButton } from '@gitlab/ui'; ...@@ -2,18 +2,18 @@ import { GlButton } from '@gitlab/ui';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import ManageViaMr from 'ee/security_configuration/components/manage_via_mr.vue';
import configureDependencyScanningMutation from 'ee/security_configuration/graphql/configure_dependency_scanning.mutation.graphql'; import configureDependencyScanningMutation from 'ee/security_configuration/graphql/configure_dependency_scanning.mutation.graphql';
import configureSecretDetectionMutation from 'ee/security_configuration/graphql/configure_secret_detection.mutation.graphql'; import configureSecretDetectionMutation from 'ee/security_configuration/graphql/configure_secret_detection.mutation.graphql';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import { buildConfigureSecurityFeatureMockFactory } from 'jest/vue_shared/security_reports/components/apollo_mocks';
import { redirectTo } from '~/lib/utils/url_utility'; import { redirectTo } from '~/lib/utils/url_utility';
import ManageViaMr from '~/vue_shared/security_configuration/components/manage_via_mr.vue';
import { import {
REPORT_TYPE_DEPENDENCY_SCANNING, REPORT_TYPE_DEPENDENCY_SCANNING,
REPORT_TYPE_SECRET_DETECTION, REPORT_TYPE_SECRET_DETECTION,
} from '~/vue_shared/security_reports/constants'; } from '~/vue_shared/security_reports/constants';
import { buildConfigureSecurityFeatureMockFactory } from './apollo_mocks';
jest.mock('~/lib/utils/url_utility'); jest.mock('~/lib/utils/url_utility');
...@@ -23,15 +23,12 @@ describe('ManageViaMr component', () => { ...@@ -23,15 +23,12 @@ describe('ManageViaMr component', () => {
let wrapper; let wrapper;
const findButton = () => wrapper.findComponent(GlButton); const findButton = () => wrapper.findComponent(GlButton);
describe.each` describe.each`
featureName | featureType | mutation | mutationType featureName | featureType | mutation | mutationId
${'Dependency Scanning'} | ${REPORT_TYPE_DEPENDENCY_SCANNING} | ${configureDependencyScanningMutation} | ${'configureDependencyScanning'} ${'SECRET_DETECTION'} | ${REPORT_TYPE_DEPENDENCY_SCANNING} | ${configureDependencyScanningMutation} | ${'configureDependencyScanning'}
${'Secret Detection'} | ${REPORT_TYPE_SECRET_DETECTION} | ${configureSecretDetectionMutation} | ${'configureSecretDetection'} ${'DEPENDENCY_SCANNING'} | ${REPORT_TYPE_SECRET_DETECTION} | ${configureSecretDetectionMutation} | ${'configureSecretDetection'}
`('$featureType', ({ featureName, featureType, mutation, mutationType }) => { `('$featureType', ({ featureName, mutation, featureType, mutationId }) => {
const buildConfigureSecurityFeatureMock = buildConfigureSecurityFeatureMockFactory( const buildConfigureSecurityFeatureMock = buildConfigureSecurityFeatureMockFactory(mutationId);
mutationType,
);
const successHandler = async () => buildConfigureSecurityFeatureMock(); const successHandler = async () => buildConfigureSecurityFeatureMock();
const noSuccessPathHandler = async () => const noSuccessPathHandler = async () =>
buildConfigureSecurityFeatureMock({ buildConfigureSecurityFeatureMock({
...@@ -53,11 +50,14 @@ describe('ManageViaMr component', () => { ...@@ -53,11 +50,14 @@ describe('ManageViaMr component', () => {
wrapper = extendedWrapper( wrapper = extendedWrapper(
mount(ManageViaMr, { mount(ManageViaMr, {
apolloProvider: mockApollo, apolloProvider: mockApollo,
provide: {
projectPath: 'testProjectPath',
},
propsData: { propsData: {
feature: { feature: {
name: featureName, name: featureName,
configured: isFeatureConfigured,
type: featureType, type: featureType,
configured: isFeatureConfigured,
}, },
}, },
}), }),
......
...@@ -28495,9 +28495,6 @@ msgstr "" ...@@ -28495,9 +28495,6 @@ msgstr ""
msgid "SecurityConfiguration|Configure via Merge Request" msgid "SecurityConfiguration|Configure via Merge Request"
msgstr "" msgstr ""
msgid "SecurityConfiguration|Configure via merge request"
msgstr ""
msgid "SecurityConfiguration|Could not retrieve configuration data. Please refresh the page, or try again later." msgid "SecurityConfiguration|Could not retrieve configuration data. Please refresh the page, or try again later."
msgstr "" msgstr ""
...@@ -28534,9 +28531,6 @@ msgstr "" ...@@ -28534,9 +28531,6 @@ msgstr ""
msgid "SecurityConfiguration|SAST Configuration" msgid "SecurityConfiguration|SAST Configuration"
msgstr "" msgstr ""
msgid "SecurityConfiguration|SAST merge request creation mutation failed"
msgstr ""
msgid "SecurityConfiguration|Security Control" msgid "SecurityConfiguration|Security Control"
msgstr "" msgstr ""
......
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import ConfigurationTable from '~/security_configuration/components/configuration_table.vue'; import ConfigurationTable from '~/security_configuration/components/configuration_table.vue';
import { scanners, UPGRADE_CTA } from '~/security_configuration/components/scanners_constants'; import { scanners, UPGRADE_CTA } from '~/security_configuration/components/constants';
import { import {
REPORT_TYPE_SAST, REPORT_TYPE_SAST,
...@@ -12,7 +12,13 @@ describe('Configuration Table Component', () => { ...@@ -12,7 +12,13 @@ describe('Configuration Table Component', () => {
let wrapper; let wrapper;
const createComponent = () => { const createComponent = () => {
wrapper = extendedWrapper(mount(ConfigurationTable, {})); wrapper = extendedWrapper(
mount(ConfigurationTable, {
provide: {
projectPath: 'testProjectPath',
},
}),
);
}; };
const findHelpLinks = () => wrapper.findAll('[data-testid="help-link"]'); const findHelpLinks = () => wrapper.findAll('[data-testid="help-link"]');
...@@ -30,8 +36,10 @@ describe('Configuration Table Component', () => { ...@@ -30,8 +36,10 @@ describe('Configuration Table Component', () => {
expect(wrapper.text()).toContain(scanner.name); expect(wrapper.text()).toContain(scanner.name);
expect(wrapper.text()).toContain(scanner.description); expect(wrapper.text()).toContain(scanner.description);
if (scanner.type === REPORT_TYPE_SAST) { if (scanner.type === REPORT_TYPE_SAST) {
expect(wrapper.findByTestId(scanner.type).text()).toBe('Configure via merge request'); expect(wrapper.findByTestId(scanner.type).text()).toBe('Configure via Merge Request');
} else if (scanner.type !== REPORT_TYPE_SECRET_DETECTION) { } else if (scanner.type === REPORT_TYPE_SECRET_DETECTION) {
expect(wrapper.findByTestId(scanner.type).exists()).toBe(false);
} else {
expect(wrapper.findByTestId(scanner.type).text()).toMatchInterpolatedText(UPGRADE_CTA); expect(wrapper.findByTestId(scanner.type).text()).toMatchInterpolatedText(UPGRADE_CTA);
} }
}); });
......
import { GlButton } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { redirectTo } from '~/lib/utils/url_utility';
import ManageSast from '~/security_configuration/components/manage_sast.vue';
import configureSastMutation from '~/security_configuration/graphql/configure_sast.mutation.graphql';
jest.mock('~/lib/utils/url_utility', () => ({
redirectTo: jest.fn(),
}));
Vue.use(VueApollo);
describe('Manage Sast Component', () => {
let wrapper;
const findButton = () => wrapper.findComponent(GlButton);
const successHandler = async () => {
return {
data: {
configureSast: {
successPath: 'testSuccessPath',
errors: [],
__typename: 'ConfigureSastPayload',
},
},
};
};
const noSuccessPathHandler = async () => {
return {
data: {
configureSast: {
successPath: '',
errors: [],
__typename: 'ConfigureSastPayload',
},
},
};
};
const errorHandler = async () => {
return {
data: {
configureSast: {
successPath: 'testSuccessPath',
errors: ['foo'],
__typename: 'ConfigureSastPayload',
},
},
};
};
const pendingHandler = () => new Promise(() => {});
function createMockApolloProvider(handler) {
const requestHandlers = [[configureSastMutation, handler]];
return createMockApollo(requestHandlers);
}
function createComponent(options = {}) {
const { mockApollo } = options;
wrapper = extendedWrapper(
mount(ManageSast, {
apolloProvider: mockApollo,
}),
);
}
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('should render Button with correct text', () => {
createComponent();
expect(findButton().text()).toContain('Configure via merge request');
});
describe('given a successful response', () => {
beforeEach(() => {
const mockApollo = createMockApolloProvider(successHandler);
createComponent({ mockApollo });
});
it('should call redirect helper with correct value', async () => {
await wrapper.trigger('click');
await waitForPromises();
expect(redirectTo).toHaveBeenCalledTimes(1);
expect(redirectTo).toHaveBeenCalledWith('testSuccessPath');
// This is done for UX reasons. If the loading prop is set to false
// on success, then there's a period where the button is clickable
// again. Instead, we want the button to display a loading indicator
// for the remainder of the lifetime of the page (i.e., until the
// browser can start painting the new page it's been redirected to).
expect(findButton().props().loading).toBe(true);
});
});
describe('given a pending response', () => {
beforeEach(() => {
const mockApollo = createMockApolloProvider(pendingHandler);
createComponent({ mockApollo });
});
it('renders spinner correctly', async () => {
expect(findButton().props('loading')).toBe(false);
await wrapper.trigger('click');
await waitForPromises();
expect(findButton().props('loading')).toBe(true);
});
});
describe.each`
handler | message
${noSuccessPathHandler} | ${'SAST merge request creation mutation failed'}
${errorHandler} | ${'foo'}
`('given an error response', ({ handler, message }) => {
beforeEach(() => {
const mockApollo = createMockApolloProvider(handler);
createComponent({ mockApollo });
});
it('should catch and emit error', async () => {
await wrapper.trigger('click');
await waitForPromises();
expect(wrapper.emitted('error')).toEqual([[message]]);
expect(findButton().props('loading')).toBe(false);
});
});
});
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import { UPGRADE_CTA } from '~/security_configuration/components/scanners_constants'; import { UPGRADE_CTA } from '~/security_configuration/components/constants';
import Upgrade from '~/security_configuration/components/upgrade.vue'; import Upgrade from '~/security_configuration/components/upgrade.vue';
const TEST_URL = 'http://www.example.test'; const TEST_URL = 'http://www.example.test';
......
import { GlButton } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { redirectTo } from '~/lib/utils/url_utility';
import configureSast from '~/security_configuration/graphql/configure_sast.mutation.graphql';
import ManageViaMr from '~/vue_shared/security_configuration/components/manage_via_mr.vue';
import { REPORT_TYPE_SAST } from '~/vue_shared/security_reports/constants';
import { buildConfigureSecurityFeatureMockFactory } from './apollo_mocks';
jest.mock('~/lib/utils/url_utility');
Vue.use(VueApollo);
describe('ManageViaMr component', () => {
let wrapper;
const findButton = () => wrapper.findComponent(GlButton);
describe.each`
featureName | featureType | mutation | mutationId
${'SAST'} | ${REPORT_TYPE_SAST} | ${configureSast} | ${'configureSast'}
`('$featureType', ({ featureName, mutation, featureType, mutationId }) => {
const buildConfigureSecurityFeatureMock = buildConfigureSecurityFeatureMockFactory(mutationId);
const successHandler = async () => buildConfigureSecurityFeatureMock();
const noSuccessPathHandler = async () =>
buildConfigureSecurityFeatureMock({
successPath: '',
});
const errorHandler = async () =>
buildConfigureSecurityFeatureMock({
errors: ['foo'],
});
const pendingHandler = () => new Promise(() => {});
function createMockApolloProvider(handler) {
const requestHandlers = [[mutation, handler]];
return createMockApollo(requestHandlers);
}
function createComponent({ mockApollo, isFeatureConfigured = false } = {}) {
wrapper = extendedWrapper(
mount(ManageViaMr, {
apolloProvider: mockApollo,
provide: {
projectPath: 'testProjectPath',
},
propsData: {
feature: {
name: featureName,
type: featureType,
configured: isFeatureConfigured,
},
},
}),
);
}
afterEach(() => {
wrapper.destroy();
});
describe('when feature is configured', () => {
beforeEach(() => {
const mockApollo = createMockApolloProvider(successHandler);
createComponent({ mockApollo, isFeatureConfigured: true });
});
it('it does not render a button', () => {
expect(findButton().exists()).toBe(false);
});
});
describe('when feature is not configured', () => {
beforeEach(() => {
const mockApollo = createMockApolloProvider(successHandler);
createComponent({ mockApollo, isFeatureConfigured: false });
});
it('it does render a button', () => {
expect(findButton().exists()).toBe(true);
});
});
describe('given a pending response', () => {
beforeEach(() => {
const mockApollo = createMockApolloProvider(pendingHandler);
createComponent({ mockApollo });
});
it('renders spinner correctly', async () => {
const button = findButton();
expect(button.props('loading')).toBe(false);
await button.trigger('click');
expect(button.props('loading')).toBe(true);
});
});
describe('given a successful response', () => {
beforeEach(() => {
const mockApollo = createMockApolloProvider(successHandler);
createComponent({ mockApollo });
});
it('should call redirect helper with correct value', async () => {
await wrapper.trigger('click');
await waitForPromises();
expect(redirectTo).toHaveBeenCalledTimes(1);
expect(redirectTo).toHaveBeenCalledWith('testSuccessPath');
// This is done for UX reasons. If the loading prop is set to false
// on success, then there's a period where the button is clickable
// again. Instead, we want the button to display a loading indicator
// for the remainder of the lifetime of the page (i.e., until the
// browser can start painting the new page it's been redirected to).
expect(findButton().props().loading).toBe(true);
});
});
describe.each`
handler | message
${noSuccessPathHandler} | ${`${featureName} merge request creation mutation failed`}
${errorHandler} | ${'foo'}
`('given an error response', ({ handler, message }) => {
beforeEach(() => {
const mockApollo = createMockApolloProvider(handler);
createComponent({ mockApollo });
});
it('should catch and emit error', async () => {
await wrapper.trigger('click');
await waitForPromises();
expect(wrapper.emitted('error')).toEqual([[message]]);
expect(findButton().props('loading')).toBe(false);
});
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment