Commit ab82aee6 authored by Vitaly Slobodin's avatar Vitaly Slobodin

Merge branch '280561-create-dast-site-validation-modal' into 'master'

Create DastSiteValidationModal component

See merge request gitlab-org/gitlab!47443
parents 865ce25b 4f5f5024
...@@ -2,84 +2,49 @@ ...@@ -2,84 +2,49 @@
import { import {
GlAlert, GlAlert,
GlButton, GlButton,
GlCard,
GlFormGroup, GlFormGroup,
GlFormInput, GlFormInput,
GlFormInputGroup, GlFormInputGroup,
GlFormRadioGroup, GlFormRadioGroup,
GlIcon,
GlInputGroupText, GlInputGroupText,
GlLoadingIcon, GlModal,
GlSkeletonLoader,
GlTruncate,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { omit } from 'lodash'; import { omit } from 'lodash';
import { __, s__ } from '~/locale';
import * as Sentry from '~/sentry/wrapper'; import * as Sentry from '~/sentry/wrapper';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import download from '~/lib/utils/downloader'; import download from '~/lib/utils/downloader';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { cleanLeadingSeparator, joinPaths, stripPathTail } from '~/lib/utils/url_utility'; import { cleanLeadingSeparator, joinPaths, stripPathTail } from '~/lib/utils/url_utility';
import { fetchPolicies } from '~/lib/graphql';
import { import {
DAST_SITE_VALIDATION_MODAL_ID,
DAST_SITE_VALIDATION_HTTP_HEADER_KEY, DAST_SITE_VALIDATION_HTTP_HEADER_KEY,
DAST_SITE_VALIDATION_METHOD_HTTP_HEADER, DAST_SITE_VALIDATION_METHOD_HTTP_HEADER,
DAST_SITE_VALIDATION_METHOD_TEXT_FILE, DAST_SITE_VALIDATION_METHOD_TEXT_FILE,
DAST_SITE_VALIDATION_METHODS, DAST_SITE_VALIDATION_METHODS,
DAST_SITE_VALIDATION_STATUS,
DAST_SITE_VALIDATION_POLL_INTERVAL,
} from '../constants'; } from '../constants';
import dastSiteTokenCreateMutation from '../graphql/dast_site_token_create.mutation.graphql';
import dastSiteValidationCreateMutation from '../graphql/dast_site_validation_create.mutation.graphql'; import dastSiteValidationCreateMutation from '../graphql/dast_site_validation_create.mutation.graphql';
import dastSiteValidationQuery from '../graphql/dast_site_validation.query.graphql';
export default { export default {
name: 'DastSiteValidation', name: 'DastSiteValidationModal',
DAST_SITE_VALIDATION_MODAL_ID,
components: { components: {
ClipboardButton,
GlAlert, GlAlert,
ClipboardButton,
GlButton, GlButton,
GlCard,
GlFormGroup, GlFormGroup,
GlFormInput, GlFormInput,
GlFormInputGroup, GlFormInputGroup,
GlFormRadioGroup, GlFormRadioGroup,
GlIcon,
GlInputGroupText, GlInputGroupText,
GlLoadingIcon, GlModal,
GlSkeletonLoader,
GlTruncate,
}, },
mixins: [glFeatureFlagsMixin()], mixins: [glFeatureFlagsMixin()],
apollo: {
dastSiteValidation: {
query: dastSiteValidationQuery,
variables() {
return {
fullPath: this.fullPath,
targetUrl: this.targetUrl,
};
},
manual: true,
result({
data: {
project: {
dastSiteValidation: { status },
},
},
}) {
if (status === DAST_SITE_VALIDATION_STATUS.PASSED) {
this.onSuccess();
}
if (status === DAST_SITE_VALIDATION_STATUS.FAILED) {
this.onError();
}
},
skip() {
return !(this.isCreatingValidation || this.isValidating);
},
pollInterval: DAST_SITE_VALIDATION_POLL_INTERVAL,
fetchPolicy: fetchPolicies.NETWORK_ONLY,
error(e) {
this.onError(e);
},
},
},
props: { props: {
fullPath: { fullPath: {
type: String, type: String,
...@@ -89,27 +54,36 @@ export default { ...@@ -89,27 +54,36 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
tokenId: {
type: String,
required: false,
default: null,
},
token: {
type: String,
required: false,
default: null,
},
}, },
data() { data() {
return { return {
isCreatingValidation: false, isCreatingToken: false,
isValidating: false, hasErrors: false,
hasValidationError: false, token: null,
tokenId: null,
validationMethod: DAST_SITE_VALIDATION_METHOD_TEXT_FILE, validationMethod: DAST_SITE_VALIDATION_METHOD_TEXT_FILE,
validationPath: '', validationPath: '',
}; };
}, },
computed: { computed: {
modalProps() {
return {
id: DAST_SITE_VALIDATION_MODAL_ID,
title: s__('DastSiteValidation|Validate target site'),
primaryProps: {
text: s__('DastSiteValidation|Validate'),
attributes: [
{ disabled: this.hasErrors },
{ variant: 'success' },
{ category: 'primary' },
{ 'data-testid': 'validate-dast-site-button' },
],
},
cancelProps: {
text: __('Cancel'),
},
};
},
validationMethodOptions() { validationMethodOptions() {
const isHttpHeaderValidationEnabled = this.glFeatures const isHttpHeaderValidationEnabled = this.glFeatures
.securityOnDemandScansHttpHeaderValidation; .securityOnDemandScansHttpHeaderValidation;
...@@ -150,8 +124,9 @@ export default { ...@@ -150,8 +124,9 @@ export default {
}, },
}, },
watch: { watch: {
targetUrl() { targetUrl: {
this.hasValidationError = false; immediate: true,
handler: 'createValidationToken',
}, },
}, },
created() { created() {
...@@ -178,128 +153,127 @@ export default { ...@@ -178,128 +153,127 @@ export default {
downloadTextFile() { downloadTextFile() {
download({ fileName: this.textFileName, fileData: btoa(this.token) }); download({ fileName: this.textFileName, fileData: btoa(this.token) });
}, },
async validate() { async createValidationToken() {
this.hasValidationError = false; this.isCreatingToken = true;
this.isCreatingValidation = true; this.hasErrors = false;
this.isValidating = true;
try { try {
const { const {
data: { data: {
dastSiteValidationCreate: { status, errors }, dastSiteTokenCreate: { errors, id, token },
}, },
} = await this.$apollo.mutate({ } = await this.$apollo.mutate({
mutation: dastSiteValidationCreateMutation, mutation: dastSiteTokenCreateMutation,
variables: { variables: {
projectFullPath: this.fullPath, fullPath: this.fullPath,
dastSiteTokenId: this.tokenId, targetUrl: this.targetUrl,
validationPath: this.validationPath,
validationStrategy: this.validationMethod,
}, },
}); });
if (errors?.length) { if (errors?.length) {
this.onError(); this.onError();
} else if (status === DAST_SITE_VALIDATION_STATUS.PASSED) {
this.onSuccess();
} else { } else {
this.isCreatingValidation = false; this.token = token;
this.tokenId = id;
} }
} catch (exception) { } catch (exception) {
this.onError(exception); this.onError(exception);
} }
this.isCreatingToken = false;
}, },
onSuccess() { async validate() {
this.isCreatingValidation = false; try {
this.isValidating = false; await this.$apollo.mutate({
this.$emit('success'); mutation: dastSiteValidationCreateMutation,
variables: {
projectFullPath: this.fullPath,
dastSiteTokenId: this.tokenId,
validationPath: this.validationPath,
validationStrategy: this.validationMethod,
},
});
} catch (exception) {
this.onError(exception);
}
}, },
onError(exception = null) { onError(exception = null) {
if (exception !== null) { if (exception !== null) {
Sentry.captureException(exception); Sentry.captureException(exception);
} }
this.isCreatingValidation = false; this.hasErrors = true;
this.isValidating = false;
this.hasValidationError = true;
}, },
}, },
}; };
</script> </script>
<template> <template>
<gl-card class="gl-bg-gray-10"> <gl-modal
<gl-alert variant="warning" :dismissible="false" class="gl-mb-3"> ref="modal"
{{ s__('DastProfiles|Site is not validated yet, please follow the steps.') }} :modal-id="modalProps.id"
:title="modalProps.title"
:action-primary="modalProps.primaryProps"
:action-cancel="modalProps.cancelProps"
v-bind="$attrs"
v-on="$listeners"
@primary="validate"
>
<template v-if="isCreatingToken">
<gl-skeleton-loader :width="768" :height="206">
<rect y="0" width="300" height="16" rx="4" />
<rect y="25" width="200" height="16" rx="4" />
<rect y="65" width="350" height="16" rx="4" />
<rect y="90" width="535" height="24" rx="4" />
<rect y="135" width="370" height="16" rx="4" />
<rect y="160" width="460" height="32" rx="4" />
</gl-skeleton-loader>
</template>
<gl-alert v-else-if="hasErrors" variant="danger" :dismissible="false">
{{ s__('DastSiteValidation|Could not create validation token. Please try again.') }}
</gl-alert> </gl-alert>
<gl-form-group :label="s__('DastProfiles|Step 1 - Choose site validation method')"> <template v-else>
<gl-form-radio-group v-model="validationMethod" :options="validationMethodOptions" /> <gl-form-group :label="s__('DastSiteValidation|Step 1 - Choose site validation method')">
</gl-form-group> <gl-form-radio-group v-model="validationMethod" :options="validationMethodOptions" />
<gl-form-group </gl-form-group>
v-if="isTextFileValidation" <gl-form-group
:label="s__('DastProfiles|Step 2 - Add following text to the target site')" v-if="isTextFileValidation"
> :label="s__('DastSiteValidation|Step 2 - Add following text to the target site')"
<gl-button >
variant="info" <gl-button
category="secondary" variant="info"
size="small" category="secondary"
icon="download" size="small"
data-testid="download-dast-text-file-validation-button" icon="download"
:aria-label="s__('DastProfiles|Download validation text file')" class="gl-display-inline-flex gl-max-w-full"
@click="downloadTextFile()" data-testid="download-dast-text-file-validation-button"
:aria-label="s__('DastSiteValidation|Download validation text file')"
@click="downloadTextFile()"
>
<gl-truncate :text="textFileName" position="middle" />
</gl-button>
</gl-form-group>
<gl-form-group
v-else-if="isHttpHeaderValidation"
:label="s__('DastSiteValidation|Step 2 - Add following HTTP header to your site')"
> >
{{ textFileName }} <code class="gl-p-3 gl-bg-black gl-text-white">{{ httpHeader }}</code>
</gl-button> <clipboard-button
</gl-form-group> :text="httpHeader"
<gl-form-group :title="s__('DastSiteValidation|Copy HTTP header to clipboard')"
v-else-if="isHttpHeaderValidation"
:label="s__('DastProfiles|Step 2 - Add following HTTP header to your site')"
>
<code class="gl-p-3 gl-bg-black gl-text-white">{{ httpHeader }}</code>
<clipboard-button
:text="httpHeader"
:title="s__('DastProfiles|Copy HTTP header to clipboard')"
/>
</gl-form-group>
<gl-form-group :label="locationStepLabel" class="mw-460">
<gl-form-input-group>
<template #prepend>
<gl-input-group-text data-testid="dast-site-validation-path-prefix">{{
origin
}}</gl-input-group-text>
</template>
<gl-form-input
v-model="validationPath"
class="gl-bg-white!"
data-testid="dast-site-validation-path-input"
@input="onValidationPathInput()"
/> />
</gl-form-input-group> </gl-form-group>
</gl-form-group> <gl-form-group :label="locationStepLabel" class="mw-460">
<gl-form-input-group>
<hr /> <template #prepend>
<gl-input-group-text data-testid="dast-site-validation-path-prefix">{{
<gl-button origin
variant="success" }}</gl-input-group-text>
category="secondary" </template>
data-testid="validate-dast-site-button" <gl-form-input
:disabled="isValidating" v-model="validationPath"
@click="validate" class="gl-bg-white!"
> data-testid="dast-site-validation-path-input"
{{ s__('DastProfiles|Validate') }} @input="onValidationPathInput()"
</gl-button> />
<span </gl-form-input-group>
class="gl-ml-3" </gl-form-group>
:class="{ 'gl-text-orange-600': isValidating, 'gl-text-red-500': hasValidationError }" </template>
> </gl-modal>
<template v-if="isValidating">
<gl-loading-icon inline /> {{ s__('DastProfiles|Validating...') }}
</template>
<template v-else-if="hasValidationError">
<gl-icon name="status_failed" />
{{
s__(
'DastProfiles|Validation failed, please make sure that you follow the steps above with the chosen method.',
)
}}
</template>
</span>
</gl-card>
</template> </template>
...@@ -6,16 +6,16 @@ export const DAST_SITE_VALIDATION_METHOD_HTTP_HEADER = 'HEADER'; ...@@ -6,16 +6,16 @@ export const DAST_SITE_VALIDATION_METHOD_HTTP_HEADER = 'HEADER';
export const DAST_SITE_VALIDATION_METHODS = { export const DAST_SITE_VALIDATION_METHODS = {
[DAST_SITE_VALIDATION_METHOD_TEXT_FILE]: { [DAST_SITE_VALIDATION_METHOD_TEXT_FILE]: {
value: DAST_SITE_VALIDATION_METHOD_TEXT_FILE, value: DAST_SITE_VALIDATION_METHOD_TEXT_FILE,
text: s__('DastProfiles|Text file validation'), text: s__('DastSiteValidation|Text file validation'),
i18n: { i18n: {
locationStepLabel: s__('DastProfiles|Step 3 - Confirm text file location and validate'), locationStepLabel: s__('DastSiteValidation|Step 3 - Confirm text file location and validate'),
}, },
}, },
[DAST_SITE_VALIDATION_METHOD_HTTP_HEADER]: { [DAST_SITE_VALIDATION_METHOD_HTTP_HEADER]: {
value: DAST_SITE_VALIDATION_METHOD_HTTP_HEADER, value: DAST_SITE_VALIDATION_METHOD_HTTP_HEADER,
text: s__('DastProfiles|Header validation'), text: s__('DastSiteValidation|Header validation'),
i18n: { i18n: {
locationStepLabel: s__('DastProfiles|Step 3 - Confirm header location and validate'), locationStepLabel: s__('DastSiteValidation|Step 3 - Confirm header location and validate'),
}, },
}, },
}; };
...@@ -27,5 +27,6 @@ export const DAST_SITE_VALIDATION_STATUS = { ...@@ -27,5 +27,6 @@ export const DAST_SITE_VALIDATION_STATUS = {
FAILED: 'FAILED_VALIDATION', FAILED: 'FAILED_VALIDATION',
}; };
export const DAST_SITE_VALIDATION_POLL_INTERVAL = 1000;
export const DAST_SITE_VALIDATION_HTTP_HEADER_KEY = 'Gitlab-On-Demand-DAST'; export const DAST_SITE_VALIDATION_HTTP_HEADER_KEY = 'Gitlab-On-Demand-DAST';
export const DAST_SITE_VALIDATION_MODAL_ID = 'dast-site-validation-modal';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
Vue.use(VueApollo);
export default new VueApollo({
defaultClient: createDefaultClient(),
});
import { DAST_SITE_VALIDATION_STATUS } from 'ee/security_configuration/dast_site_profiles_form/constants';
export const dastSiteProfileCreate = (errors = []) => ({ export const dastSiteProfileCreate = (errors = []) => ({
data: { dastSiteProfileCreate: { id: '3083', errors } }, data: { dastSiteProfileCreate: { id: '3083', errors } },
}); });
...@@ -7,23 +5,3 @@ export const dastSiteProfileCreate = (errors = []) => ({ ...@@ -7,23 +5,3 @@ export const dastSiteProfileCreate = (errors = []) => ({
export const dastSiteProfileUpdate = (errors = []) => ({ export const dastSiteProfileUpdate = (errors = []) => ({
data: { dastSiteProfileUpdate: { id: '3083', errors } }, data: { dastSiteProfileUpdate: { id: '3083', errors } },
}); });
export const dastSiteValidation = (status = DAST_SITE_VALIDATION_STATUS.PENDING) => ({
data: { project: { dastSiteValidation: { status, id: '1' } } },
});
export const dastSiteValidationCreate = (errors = []) => ({
data: {
dastSiteValidationCreate: { status: DAST_SITE_VALIDATION_STATUS.PENDING, id: '1', errors },
},
});
export const dastSiteTokenCreate = ({ id = '1', token = '1', errors = [] }) => ({
data: {
dastSiteTokenCreate: {
id,
token,
errors,
},
},
});
import { GlLoadingIcon } from '@gitlab/ui';
import { within } from '@testing-library/dom'; import { within } from '@testing-library/dom';
import { createLocalVue, mount, shallowMount, createWrapper } from '@vue/test-utils'; import { createLocalVue, mount, shallowMount, createWrapper } from '@vue/test-utils';
import merge from 'lodash/merge'; import merge from 'lodash/merge';
import { createMockClient } from 'mock-apollo-client'; import createApolloProvider from 'helpers/mock_apollo_helper';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import DastSiteValidation from 'ee/security_configuration/dast_site_profiles_form/components/dast_site_validation.vue'; import dastSiteValidationCreateMutation from 'ee/security_configuration/dast_site_validation/graphql/dast_site_validation_create.mutation.graphql';
import { DAST_SITE_VALIDATION_STATUS } from 'ee/security_configuration/dast_site_profiles_form/constants'; import dastSiteTokenCreateMutation from 'ee/security_configuration/dast_site_validation/graphql/dast_site_token_create.mutation.graphql';
import dastSiteValidationQuery from 'ee/security_configuration/dast_site_profiles_form/graphql/dast_site_validation.query.graphql';
import dastSiteValidationCreateMutation from 'ee/security_configuration/dast_site_profiles_form/graphql/dast_site_validation_create.mutation.graphql';
import * as responses from 'ee_jest/security_configuration/dast_site_profiles_form/mock_data/apollo_mock';
import waitForPromises from 'jest/helpers/wait_for_promises'; import waitForPromises from 'jest/helpers/wait_for_promises';
import { GlAlert, GlFormGroup, GlModal, GlSkeletonLoader } from '@gitlab/ui';
import DastSiteValidationModal from 'ee/security_configuration/dast_site_validation/components/dast_site_validation_modal.vue';
import * as responses from '../mock_data/apollo_mock';
import download from '~/lib/utils/downloader'; import download from '~/lib/utils/downloader';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
...@@ -28,49 +27,26 @@ const validationMethods = ['text file', 'header']; ...@@ -28,49 +27,26 @@ const validationMethods = ['text file', 'header'];
const defaultProps = { const defaultProps = {
fullPath, fullPath,
targetUrl, targetUrl,
tokenId,
token,
}; };
const defaultRequestHandlers = { const defaultRequestHandlers = {
dastSiteValidation: jest.fn().mockResolvedValue(responses.dastSiteValidation()), dastSiteTokenCreate: jest
.fn()
.mockResolvedValue(responses.dastSiteTokenCreate({ id: tokenId, token })),
dastSiteValidationCreate: jest.fn().mockResolvedValue(responses.dastSiteValidationCreate()), dastSiteValidationCreate: jest.fn().mockResolvedValue(responses.dastSiteValidationCreate()),
}; };
describe('DastSiteValidation', () => { describe('DastSiteValidationModal', () => {
let wrapper; let wrapper;
let apolloProvider;
let requestHandlers; let requestHandlers;
const mockClientFactory = handlers => { const componentFactory = (mountFn = shallowMount) => ({
const mockClient = createMockClient(); mountOptions = {},
handlers = {},
requestHandlers = { } = {}) => {
...defaultRequestHandlers, requestHandlers = { ...defaultRequestHandlers, ...handlers };
...handlers,
};
mockClient.setRequestHandler(dastSiteValidationQuery, requestHandlers.dastSiteValidation);
mockClient.setRequestHandler(
dastSiteValidationCreateMutation,
requestHandlers.dastSiteValidationCreate,
);
return mockClient;
};
const respondWith = handlers => {
apolloProvider.defaultClient = mockClientFactory(handlers);
};
const componentFactory = (mountFn = shallowMount) => options => {
apolloProvider = new VueApollo({
defaultClient: mockClientFactory(),
});
wrapper = mountFn( wrapper = mountFn(
DastSiteValidation, DastSiteValidationModal,
merge( merge(
{}, {},
{ {
...@@ -78,11 +54,18 @@ describe('DastSiteValidation', () => { ...@@ -78,11 +54,18 @@ describe('DastSiteValidation', () => {
provide: { provide: {
glFeatures: { securityOnDemandScansHttpHeaderValidation: true }, glFeatures: { securityOnDemandScansHttpHeaderValidation: true },
}, },
attrs: {
static: true,
visible: true,
},
}, },
options, mountOptions,
{ {
localVue, localVue,
apolloProvider, apolloProvider: createApolloProvider([
[dastSiteTokenCreateMutation, requestHandlers.dastSiteTokenCreate],
[dastSiteValidationCreateMutation, requestHandlers.dastSiteValidationCreate],
]),
}, },
), ),
); );
...@@ -90,17 +73,12 @@ describe('DastSiteValidation', () => { ...@@ -90,17 +73,12 @@ describe('DastSiteValidation', () => {
const createComponent = componentFactory(); const createComponent = componentFactory();
const createFullComponent = componentFactory(mount); const createFullComponent = componentFactory(mount);
const withinComponent = () => within(wrapper.element); const withinComponent = () => within(wrapper.find(GlModal).element);
const findByTestId = id => wrapper.find(`[data-testid="${id}"`); const findByTestId = id => wrapper.find(`[data-testid="${id}"`);
const findDownloadButton = () => findByTestId('download-dast-text-file-validation-button'); const findDownloadButton = () => findByTestId('download-dast-text-file-validation-button');
const findValidationPathPrefix = () => findByTestId('dast-site-validation-path-prefix'); const findValidationPathPrefix = () => findByTestId('dast-site-validation-path-prefix');
const findValidationPathInput = () => findByTestId('dast-site-validation-path-input'); const findValidationPathInput = () => findByTestId('dast-site-validation-path-input');
const findValidateButton = () => findByTestId('validate-dast-site-button'); const findValidateButton = () => findByTestId('validate-dast-site-button');
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
const findErrorMessage = () =>
withinComponent().queryByText(
/validation failed, please make sure that you follow the steps above with the chosen method./i,
);
const findRadioInputForValidationMethod = validationMethod => const findRadioInputForValidationMethod = validationMethod =>
withinComponent().queryByRole('radio', { withinComponent().queryByRole('radio', {
name: new RegExp(`${validationMethod} validation`, 'i'), name: new RegExp(`${validationMethod} validation`, 'i'),
...@@ -113,27 +91,74 @@ describe('DastSiteValidation', () => { ...@@ -113,27 +91,74 @@ describe('DastSiteValidation', () => {
}); });
describe('rendering', () => { describe('rendering', () => {
beforeEach(() => { describe('loading', () => {
createFullComponent(); beforeEach(() => {
}); createFullComponent();
});
it('renders a download button containing the token', () => { it('renders a skeleton loader, no alert and no form group while token is being created', () => {
const downloadButton = withinComponent().getByRole('button', { expect(wrapper.find(GlSkeletonLoader).exists()).toBe(true);
name: 'Download validation text file', expect(wrapper.find(GlAlert).exists()).toBe(false);
expect(wrapper.find(GlFormGroup).exists()).toBe(false);
}); });
expect(downloadButton).not.toBeNull();
}); });
it.each(validationMethods)('renders a radio input for "%s" validation', validationMethod => { describe('error', () => {
expect(findRadioInputForValidationMethod(validationMethod)).not.toBe(null); beforeEach(async () => {
createFullComponent({
handlers: {
dastSiteTokenCreate: jest.fn().mockRejectedValue(new Error('GraphQL Network Error')),
},
});
await waitForPromises();
});
it('renders an alert and no skeleton loader or form group if token could not be created', () => {
expect(wrapper.find(GlAlert).exists()).toBe(true);
expect(wrapper.find(GlSkeletonLoader).exists()).toBe(false);
expect(wrapper.find(GlFormGroup).exists()).toBe(false);
});
}); });
it('renders an input group with the target URL prepended', () => { describe('loaded', () => {
const inputGroup = withinComponent().getByRole('group', { beforeEach(async () => {
name: 'Step 3 - Confirm text file location and validate', createFullComponent();
await waitForPromises();
});
it('renders form groups, no alert and no skeleton loader', () => {
expect(wrapper.find(GlFormGroup).exists()).toBe(true);
expect(wrapper.find(GlAlert).exists()).toBe(false);
expect(wrapper.find(GlSkeletonLoader).exists()).toBe(false);
}); });
expect(inputGroup).not.toBeNull();
expect(inputGroup.textContent).toContain(targetUrl); it('renders a download button containing the token', () => {
const downloadButton = withinComponent().getByRole('button', {
name: 'Download validation text file',
});
expect(downloadButton).not.toBeNull();
});
it.each(validationMethods)('renders a radio input for "%s" validation', validationMethod => {
expect(findRadioInputForValidationMethod(validationMethod)).not.toBe(null);
});
it('renders an input group with the target URL prepended', () => {
const inputGroup = withinComponent().getByRole('group', {
name: 'Step 3 - Confirm text file location and validate',
});
expect(inputGroup).not.toBeNull();
expect(inputGroup.textContent).toContain(targetUrl);
});
});
});
it('triggers the dastSiteTokenCreate GraphQL mutation', () => {
createComponent();
expect(requestHandlers.dastSiteTokenCreate).toHaveBeenCalledWith({
fullPath,
targetUrl,
}); });
}); });
...@@ -157,12 +182,13 @@ describe('DastSiteValidation', () => { ...@@ -157,12 +182,13 @@ describe('DastSiteValidation', () => {
({ targetUrl: url, expectedPrefix, expectedPath, expectedTextFilePath }) => { ({ targetUrl: url, expectedPrefix, expectedPath, expectedTextFilePath }) => {
beforeEach(async () => { beforeEach(async () => {
createFullComponent({ createFullComponent({
propsData: { mountOptions: {
targetUrl: url, propsData: {
targetUrl: url,
},
}, },
}); });
await waitForPromises();
await wrapper.vm.$nextTick();
enableValidationMethod(validationMethod); enableValidationMethod(validationMethod);
}); });
...@@ -182,6 +208,7 @@ describe('DastSiteValidation', () => { ...@@ -182,6 +208,7 @@ describe('DastSiteValidation', () => {
it("input value isn't automatically updated if it has been changed manually", async () => { it("input value isn't automatically updated if it has been changed manually", async () => {
createFullComponent(); createFullComponent();
await waitForPromises();
const customValidationPath = 'custom/validation/path.txt'; const customValidationPath = 'custom/validation/path.txt';
findValidationPathInput().setValue(customValidationPath); findValidationPathInput().setValue(customValidationPath);
await wrapper.setProps({ await wrapper.setProps({
...@@ -193,8 +220,9 @@ describe('DastSiteValidation', () => { ...@@ -193,8 +220,9 @@ describe('DastSiteValidation', () => {
}); });
describe('text file validation', () => { describe('text file validation', () => {
it('clicking on the download button triggers a download of a text file containing the token', () => { it('clicking on the download button triggers a download of a text file containing the token', async () => {
createComponent(); createComponent();
await waitForPromises();
findDownloadButton().vm.$emit('click'); findDownloadButton().vm.$emit('click');
expect(download).toHaveBeenCalledWith({ expect(download).toHaveBeenCalledWith({
...@@ -208,7 +236,7 @@ describe('DastSiteValidation', () => { ...@@ -208,7 +236,7 @@ describe('DastSiteValidation', () => {
beforeEach(async () => { beforeEach(async () => {
createFullComponent(); createFullComponent();
await wrapper.vm.$nextTick(); await waitForPromises();
enableValidationMethod('header'); enableValidationMethod('header');
}); });
...@@ -257,8 +285,9 @@ describe('DastSiteValidation', () => { ...@@ -257,8 +285,9 @@ describe('DastSiteValidation', () => {
}); });
describe.each(validationMethods)('"%s" validation submission', validationMethod => { describe.each(validationMethods)('"%s" validation submission', validationMethod => {
beforeEach(() => { beforeEach(async () => {
createFullComponent(); createFullComponent();
await waitForPromises();
}); });
describe('passed', () => { describe('passed', () => {
...@@ -266,15 +295,6 @@ describe('DastSiteValidation', () => { ...@@ -266,15 +295,6 @@ describe('DastSiteValidation', () => {
enableValidationMethod(validationMethod); enableValidationMethod(validationMethod);
}); });
it('while validating, shows a loading state', async () => {
findValidateButton().trigger('click');
await wrapper.vm.$nextTick();
expect(findLoadingIcon().exists()).toBe(true);
expect(wrapper.text()).toContain('Validating...');
});
it('triggers the dastSiteValidationCreate GraphQL mutation', () => { it('triggers the dastSiteValidationCreate GraphQL mutation', () => {
findValidateButton().trigger('click'); findValidateButton().trigger('click');
...@@ -285,60 +305,6 @@ describe('DastSiteValidation', () => { ...@@ -285,60 +305,6 @@ describe('DastSiteValidation', () => {
validationStrategy: wrapper.vm.validationMethod, validationStrategy: wrapper.vm.validationMethod,
}); });
}); });
it('on success, emits success event', async () => {
respondWith({
dastSiteValidation: jest
.fn()
.mockResolvedValue(responses.dastSiteValidation('PASSED_VALIDATION')),
});
findValidateButton().trigger('click');
await waitForPromises();
expect(wrapper.emitted('success')).toHaveLength(1);
});
});
describe('failed', () => {
beforeEach(() => {
respondWith({
dastSiteValidation: () =>
Promise.resolve(responses.dastSiteValidation(DAST_SITE_VALIDATION_STATUS.FAILED)),
});
});
it('shows failure message', async () => {
expect(findErrorMessage()).toBe(null);
findValidateButton().vm.$emit('click');
await waitForPromises();
expect(findErrorMessage()).not.toBe(null);
});
});
describe.each`
errorKind | errorResponse
${'top level error'} | ${() => Promise.reject(new Error('GraphQL Network Error'))}
${'errors as data'} | ${() => Promise.resolve(responses.dastSiteValidationCreate(['error#1', 'error#2']))}
`('$errorKind', ({ errorResponse }) => {
beforeEach(() => {
respondWith({
dastSiteValidationCreate: errorResponse,
});
});
it('on error, shows error state', async () => {
expect(findErrorMessage()).toBe(null);
findValidateButton().vm.$emit('click');
await waitForPromises();
expect(findErrorMessage()).not.toBe(null);
});
}); });
}); });
}); });
import { DAST_SITE_VALIDATION_STATUS } from 'ee/security_configuration/dast_site_validation/constants';
export const dastSiteValidationCreate = (errors = []) => ({
data: {
dastSiteValidationCreate: { status: DAST_SITE_VALIDATION_STATUS.PENDING, id: '1', errors },
},
});
export const dastSiteTokenCreate = ({ id = '1', token = '1', errors = [] }) => ({
data: {
dastSiteTokenCreate: {
id,
token,
errors,
},
},
});
...@@ -8434,9 +8434,6 @@ msgstr "" ...@@ -8434,9 +8434,6 @@ msgstr ""
msgid "DastProfiles|Authentication URL" msgid "DastProfiles|Authentication URL"
msgstr "" msgstr ""
msgid "DastProfiles|Copy HTTP header to clipboard"
msgstr ""
msgid "DastProfiles|Could not create the scanner profile. Please try again." msgid "DastProfiles|Could not create the scanner profile. Please try again."
msgstr "" msgstr ""
...@@ -8482,9 +8479,6 @@ msgstr "" ...@@ -8482,9 +8479,6 @@ msgstr ""
msgid "DastProfiles|Do you want to discard your changes?" msgid "DastProfiles|Do you want to discard your changes?"
msgstr "" msgstr ""
msgid "DastProfiles|Download validation text file"
msgstr ""
msgid "DastProfiles|Edit scanner profile" msgid "DastProfiles|Edit scanner profile"
msgstr "" msgstr ""
...@@ -8497,9 +8491,6 @@ msgstr "" ...@@ -8497,9 +8491,6 @@ msgstr ""
msgid "DastProfiles|Error Details" msgid "DastProfiles|Error Details"
msgstr "" msgstr ""
msgid "DastProfiles|Header validation"
msgstr ""
msgid "DastProfiles|Hide debug messages" msgid "DastProfiles|Hide debug messages"
msgstr "" msgstr ""
...@@ -8572,58 +8563,64 @@ msgstr "" ...@@ -8572,58 +8563,64 @@ msgstr ""
msgid "DastProfiles|Site Profiles" msgid "DastProfiles|Site Profiles"
msgstr "" msgstr ""
msgid "DastProfiles|Site is not validated yet, please follow the steps." msgid "DastProfiles|Spider timeout"
msgstr "" msgstr ""
msgid "DastProfiles|Spider timeout" msgid "DastProfiles|Target URL"
msgstr ""
msgid "DastProfiles|Target timeout"
msgstr ""
msgid "DastProfiles|The maximum number of minutes allowed for the spider to traverse the site."
msgstr "" msgstr ""
msgid "DastProfiles|Step 1 - Choose site validation method" msgid "DastProfiles|The maximum number of seconds allowed for the site under test to respond to a request."
msgstr "" msgstr ""
msgid "DastProfiles|Step 2 - Add following HTTP header to your site" msgid "DastProfiles|Turn on AJAX spider"
msgstr "" msgstr ""
msgid "DastProfiles|Step 2 - Add following text to the target site" msgid "DastProfiles|Username"
msgstr "" msgstr ""
msgid "DastProfiles|Step 3 - Confirm header location and validate" msgid "DastProfiles|Username form field"
msgstr "" msgstr ""
msgid "DastProfiles|Step 3 - Confirm text file location and validate" msgid "DastSiteValidation|Copy HTTP header to clipboard"
msgstr "" msgstr ""
msgid "DastProfiles|Target URL" msgid "DastSiteValidation|Could not create validation token. Please try again."
msgstr "" msgstr ""
msgid "DastProfiles|Target timeout" msgid "DastSiteValidation|Download validation text file"
msgstr "" msgstr ""
msgid "DastProfiles|Text file validation" msgid "DastSiteValidation|Header validation"
msgstr "" msgstr ""
msgid "DastProfiles|The maximum number of minutes allowed for the spider to traverse the site." msgid "DastSiteValidation|Step 1 - Choose site validation method"
msgstr "" msgstr ""
msgid "DastProfiles|The maximum number of seconds allowed for the site under test to respond to a request." msgid "DastSiteValidation|Step 2 - Add following HTTP header to your site"
msgstr "" msgstr ""
msgid "DastProfiles|Turn on AJAX spider" msgid "DastSiteValidation|Step 2 - Add following text to the target site"
msgstr "" msgstr ""
msgid "DastProfiles|Username" msgid "DastSiteValidation|Step 3 - Confirm header location and validate"
msgstr "" msgstr ""
msgid "DastProfiles|Username form field" msgid "DastSiteValidation|Step 3 - Confirm text file location and validate"
msgstr "" msgstr ""
msgid "DastProfiles|Validate" msgid "DastSiteValidation|Text file validation"
msgstr "" msgstr ""
msgid "DastProfiles|Validating..." msgid "DastSiteValidation|Validate"
msgstr "" msgstr ""
msgid "DastProfiles|Validation failed, please make sure that you follow the steps above with the chosen method." msgid "DastSiteValidation|Validate target site"
msgstr "" msgstr ""
msgid "Data is still calculating..." msgid "Data is still calculating..."
......
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