Commit c3577731 authored by Paul Slaughter's avatar Paul Slaughter

Merge branch '247106-dast-site-text-file-validation-paths' into 'master'

DAST site profiles: support validation path

See merge request gitlab-org/gitlab!42906
parents cdb43d98 0f692c15
......@@ -16,7 +16,7 @@ function decodeUrlParameter(val) {
return decodeURIComponent(val.replace(/\+/g, '%20'));
}
function cleanLeadingSeparator(path) {
export function cleanLeadingSeparator(path) {
return path.replace(PATH_SEPARATOR_LEADING_REGEX, '');
}
......@@ -435,3 +435,12 @@ export function getHTTPProtocol(url) {
const protocol = url.split(':');
return protocol.length > 1 ? protocol[0] : undefined;
}
/**
* Strips the filename from the given path by removing every non-slash character from the end of the
* passed parameter.
* @param {string} path
*/
export function stripPathTail(path = '') {
return path.replace(/[^/]+$/, '');
}
......@@ -13,6 +13,7 @@ import {
GlLoadingIcon,
} from '@gitlab/ui';
import download from '~/lib/utils/downloader';
import { cleanLeadingSeparator, joinPaths, stripPathTail } from '~/lib/utils/url_utility';
import {
DAST_SITE_VALIDATION_METHOD_TEXT_FILE,
DAST_SITE_VALIDATION_METHODS,
......@@ -91,9 +92,23 @@ export default {
isValidating: false,
hasValidationError: false,
validationMethod: DAST_SITE_VALIDATION_METHOD_TEXT_FILE,
validationPath: '',
};
},
computed: {
urlObject() {
try {
return new URL(this.targetUrl);
} catch {
return {};
}
},
origin() {
return this.urlObject.origin ? `${this.urlObject.origin}/` : '';
},
path() {
return cleanLeadingSeparator(this.urlObject.pathname || '');
},
isTextFileValidation() {
return this.validationMethod === DAST_SITE_VALIDATION_METHOD_TEXT_FILE;
},
......@@ -109,7 +124,17 @@ export default {
this.hasValidationError = false;
},
},
created() {
this.updateValidationPath();
this.unsubscribe = this.$watch(() => this.token, this.updateValidationPath);
},
methods: {
updateValidationPath() {
this.validationPath = joinPaths(stripPathTail(this.path), this.textFileName);
},
onValidationPathInput() {
this.unsubscribe();
},
downloadTextFile() {
download({ fileName: this.textFileName, fileData: btoa(this.token) });
},
......@@ -127,6 +152,7 @@ export default {
variables: {
projectFullPath: this.fullPath,
dastSiteTokenId: this.tokenId,
validationPath: this.validationPath,
strategy: this.validationMethod,
},
});
......@@ -186,9 +212,16 @@ export default {
<gl-form-group :label="locationStepLabel" class="mw-460">
<gl-form-input-group>
<template #prepend>
<gl-input-group-text>{{ targetUrl }}</gl-input-group-text>
<gl-input-group-text data-testid="dast-site-validation-path-prefix">{{
origin
}}</gl-input-group-text>
</template>
<gl-form-input class="gl-bg-white!" />
<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>
......
mutation dastSiteValidationCreate(
$projectFullPath: ID!
$dastSiteTokenId: DastSiteTokenID!
$validationPath: String!
$validationStrategy: DastSiteValidationStrategyEnum
) {
dastSiteValidationCreate(
input: {
projectFullPath: $projectFullPath
fullPath: $projectFullPath
dastSiteTokenId: $dastSiteTokenId
validationPath: $validationPath
strategy: $validationStrategy
}
) {
id
status
errors
}
......
......@@ -18,11 +18,13 @@ localVue.use(VueApollo);
const fullPath = 'group/project';
const targetUrl = 'https://example.com/';
const tokenId = '1';
const token = 'validation-token-123';
const defaultProps = {
fullPath,
targetUrl,
tokenId,
token,
};
......@@ -82,9 +84,11 @@ describe('DastSiteValidation', () => {
const createFullComponent = componentFactory(mount);
const withinComponent = () => within(wrapper.element);
const findDownloadButton = () =>
wrapper.find('[data-testid="download-dast-text-file-validation-button"]');
const findValidateButton = () => wrapper.find('[data-testid="validate-dast-site-button"]');
const findByTestId = id => wrapper.find(`[data-testid="${id}"`);
const findDownloadButton = () => findByTestId('download-dast-text-file-validation-button');
const findValidationPathPrefix = () => findByTestId('dast-site-validation-path-prefix');
const findValidationPathInput = () => findByTestId('dast-site-validation-path-input');
const findValidateButton = () => findByTestId('validate-dast-site-button');
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
const findErrorMessage = () =>
withinComponent().queryByText(
......@@ -121,17 +125,58 @@ describe('DastSiteValidation', () => {
});
describe('text file validation', () => {
beforeEach(() => {
createComponent();
});
it('clicking on the download button triggers a download of a text file containing the token', () => {
createComponent();
findDownloadButton().vm.$emit('click');
expect(download).toHaveBeenCalledWith({
fileName: `GitLab-DAST-Site-Validation-${token}.txt`,
fileData: btoa(token),
});
});
describe.each`
targetUrl | expectedPrefix | expectedValue
${'https://example.com'} | ${'https://example.com/'} | ${'GitLab-DAST-Site-Validation-validation-token-123.txt'}
${'https://example.com/'} | ${'https://example.com/'} | ${'GitLab-DAST-Site-Validation-validation-token-123.txt'}
${'https://example.com/foo/bar'} | ${'https://example.com/'} | ${'foo/GitLab-DAST-Site-Validation-validation-token-123.txt'}
${'https://example.com/foo/bar/'} | ${'https://example.com/'} | ${'foo/bar/GitLab-DAST-Site-Validation-validation-token-123.txt'}
${'https://sub.example.com/foo/bar'} | ${'https://sub.example.com/'} | ${'foo/GitLab-DAST-Site-Validation-validation-token-123.txt'}
${'https://example.com/foo/index.html'} | ${'https://example.com/'} | ${'foo/GitLab-DAST-Site-Validation-validation-token-123.txt'}
${'https://example.com/foo/?bar="baz"'} | ${'https://example.com/'} | ${'foo/GitLab-DAST-Site-Validation-validation-token-123.txt'}
${'https://example.com:3000'} | ${'https://example.com:3000/'} | ${'GitLab-DAST-Site-Validation-validation-token-123.txt'}
${''} | ${''} | ${'GitLab-DAST-Site-Validation-validation-token-123.txt'}
`(
'validation path input when target URL is $targetUrl',
({ targetUrl: url, expectedPrefix, expectedValue }) => {
beforeEach(() => {
createFullComponent({
propsData: {
targetUrl: url,
},
});
});
it(`prefix is set to ${expectedPrefix}`, () => {
expect(findValidationPathPrefix().text()).toBe(expectedPrefix);
});
it(`input value defaults to ${expectedValue}`, () => {
expect(findValidationPathInput().element.value).toBe(expectedValue);
});
},
);
it("input value isn't automatically updated if it has been changed manually", async () => {
createFullComponent();
const customValidationPath = 'custom/validation/path.txt';
findValidationPathInput().setValue(customValidationPath);
await wrapper.setProps({
token: 'a-completely-new-token',
});
expect(findValidationPathInput().element.value).toBe(customValidationPath);
});
});
describe('validation', () => {
......@@ -149,6 +194,15 @@ describe('DastSiteValidation', () => {
expect(wrapper.text()).toContain('Validating...');
});
it('triggers the dastSiteValidationCreate GraphQL mutation', () => {
expect(requestHandlers.dastSiteValidationCreate).toHaveBeenCalledWith({
projectFullPath: fullPath,
dastSiteTokenId: tokenId,
validationPath: wrapper.vm.validationPath,
strategy: wrapper.vm.validationMethod,
});
});
it('on success, emits success event', async () => {
await waitForPromises();
......
......@@ -664,6 +664,19 @@ describe('URL utility', () => {
});
});
describe('cleanLeadingSeparator', () => {
it.each`
path | expected
${'/foo/bar'} | ${'foo/bar'}
${'foo/bar'} | ${'foo/bar'}
${'//foo/bar'} | ${'foo/bar'}
${'/./foo/bar'} | ${'./foo/bar'}
${''} | ${''}
`('$path becomes $expected', ({ path, expected }) => {
expect(urlUtils.cleanLeadingSeparator(path)).toBe(expected);
});
});
describe('joinPaths', () => {
it.each`
paths | expected
......@@ -787,4 +800,18 @@ describe('URL utility', () => {
expect(urlUtils.getHTTPProtocol(url)).toBe(expectation);
});
});
describe('stripPathTail', () => {
it.each`
path | expected
${''} | ${''}
${'index.html'} | ${''}
${'/'} | ${'/'}
${'/foo/bar'} | ${'/foo/'}
${'/foo/bar/'} | ${'/foo/bar/'}
${'/foo/bar/index.html'} | ${'/foo/bar/'}
`('strips the filename from $path => $expected', ({ path, expected }) => {
expect(urlUtils.stripPathTail(path)).toBe(expected);
});
});
});
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