Commit 2a67c3ff authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '14006-vulnerability-details-prop-ee' into 'master'

Refactor vulnerability-details component for better reusability

See merge request gitlab-org/gitlab!25830
parents b3cc472c 10e20f3a
import Vue from 'vue';
import getFileLocation from 'ee/vue_shared/security_reports/store/utils/get_file_location';
import { s__, __ } from '~/locale';
import { visitUrl } from '~/lib/utils/url_utility';
import * as types from './mutation_types';
......@@ -75,57 +74,12 @@ export default {
},
[types.SET_MODAL_DATA](state, payload) {
const { vulnerability } = payload;
const { location } = vulnerability;
Vue.set(state.modal, 'title', vulnerability.name);
Vue.set(state.modal.data.description, 'value', vulnerability.description);
Vue.set(
state.modal.data.project,
'value',
vulnerability.project && vulnerability.project.full_name,
);
Vue.set(
state.modal.data.project,
'url',
vulnerability.project && vulnerability.project.full_path,
);
Vue.set(
state.modal.data.identifiers,
'value',
vulnerability.identifiers.length && vulnerability.identifiers,
);
if (location) {
const {
file,
start_line: startLine,
end_line: endLine,
image,
operating_system: namespace,
class: className,
} = location;
const fileLocation = getFileLocation(location);
Vue.set(state.modal.project, 'value', vulnerability.project?.full_name);
Vue.set(state.modal.project, 'url', vulnerability.project?.full_path);
let lineSuffix = '';
if (startLine) {
lineSuffix += `:${startLine}`;
if (endLine && startLine !== endLine) {
lineSuffix += `-${endLine}`;
}
}
Vue.set(state.modal.data.className, 'value', className);
Vue.set(state.modal.data.file, 'value', file ? `${file}${lineSuffix}` : null);
Vue.set(state.modal.data.image, 'value', image);
Vue.set(state.modal.data.namespace, 'value', namespace);
Vue.set(state.modal.data.url, 'value', fileLocation);
Vue.set(state.modal.data.url, 'url', fileLocation);
}
Vue.set(state.modal.data.severity, 'value', vulnerability.severity);
Vue.set(state.modal.data.reportType, 'value', vulnerability.report_type);
Vue.set(state.modal, 'vulnerability', vulnerability);
Vue.set(
state.modal.vulnerability,
......@@ -143,18 +97,6 @@ export default {
Vue.set(state.modal.vulnerability, 'isDismissed', Boolean(vulnerability.dismissal_feedback));
Vue.set(state.modal, 'error', null);
Vue.set(state.modal, 'isCommentingOnDismissal', false);
if (vulnerability.instances && vulnerability.instances.length) {
Vue.set(state.modal.data.instances, 'value', vulnerability.instances);
} else {
Vue.set(state.modal.data.instances, 'value', null);
}
if (vulnerability.links && vulnerability.links.length) {
Vue.set(state.modal.data.links, 'value', vulnerability.links);
} else {
Vue.set(state.modal.data.links, 'value', null);
}
},
[types.REQUEST_CREATE_ISSUE](state) {
state.isCreatingIssue = true;
......
import { __, s__ } from '~/locale';
export default () => ({
isLoadingVulnerabilities: true,
errorLoadingVulnerabilities: false,
......@@ -21,24 +19,8 @@ export default () => ({
activeVulnerability: null,
sourceBranch: null,
modal: {
data: {
description: { text: s__('Vulnerability|Description') },
project: {
text: s__('Vulnerability|Project'),
isLink: true,
},
url: { text: __('URL'), isLink: true },
file: { text: s__('Vulnerability|File') },
identifiers: { text: s__('Vulnerability|Identifiers') },
severity: { text: s__('Vulnerability|Severity') },
reportType: { text: s__('Vulnerability|Report Type') },
className: { text: s__('Vulnerability|Class') },
image: { text: s__('Vulnerability|Image') },
namespace: { text: s__('Vulnerability|Namespace') },
links: { text: s__('Vulnerability|Links') },
instances: { text: s__('Vulnerability|Instances') },
},
vulnerability: {},
project: {},
isCreatingNewIssue: false,
isCreatingMergeRequest: false,
isDismissingVulnerability: false,
......
......@@ -86,7 +86,7 @@ export default {
);
},
project() {
return this.modal.data.project;
return this.modal.project;
},
solution() {
return this.vulnerability && this.vulnerability.solution;
......@@ -122,22 +122,6 @@ export default {
isEditingExistingFeedback() {
return this.dismissalFeedback && this.modal.isCommentingOnDismissal;
},
valuedFields() {
const { data } = this.modal;
const result = {};
Object.keys(data).forEach(key => {
if (data[key].value && data[key].value.length) {
result[key] = data[key];
if (key === 'file' && this.vulnerability.blob_path) {
result[key].isLink = true;
result[key].url = this.vulnerability.blob_path;
}
}
});
return result;
},
dismissalFeedbackObject() {
if (this.dismissalFeedback) {
return this.dismissalFeedback;
......@@ -153,7 +137,6 @@ export default {
} = gon;
return {
project_id: this.project ? this.project.id : null,
author: {
id: current_user_id,
name: current_user_fullname,
......@@ -203,7 +186,7 @@ export default {
class="modal-security-report-dast"
>
<slot>
<vulnerability-details :details="valuedFields" class="js-vulnerability-details" />
<vulnerability-details :vulnerability="vulnerability" class="js-vulnerability-details" />
<solution-card
:solution="solution"
......
<script>
export default {
name: 'VulnerabilityDetail',
props: {
label: {
type: String,
required: true,
},
},
};
</script>
<template functional>
<div class="d-sm-flex my-sm-2 my-4">
<label class="col-sm-2 text-sm-right font-weight-bold pl-0">{{ props.label }}:</label>
<div class="col-sm-10 pl-0 text-secondary">
<slot></slot>
</div>
</div>
</template>
......@@ -2,40 +2,78 @@
import { GlFriendlyWrap } from '@gitlab/ui';
import SafeLink from 'ee/vue_shared/components/safe_link.vue';
import Icon from '~/vue_shared/components/icon.vue';
import ExpandButton from '~/vue_shared/components/expand_button.vue';
import SeverityBadge from './severity_badge.vue';
import getFileLocation from '../store/utils/get_file_location';
import VulnerabilityDetail from './vulnerability_detail.vue';
export default {
name: 'VulnerabilityDetails',
components: {
ExpandButton,
GlFriendlyWrap,
Icon,
SafeLink,
SeverityBadge,
VulnerabilityDetail,
},
props: {
details: {
vulnerability: {
type: Object,
required: true,
},
},
methods: {
hasMoreValues(index, values) {
return index < values.length - 1;
computed: {
url() {
return getFileLocation(this.vulnerability.location);
},
file() {
const file = this.vulnerability?.location?.file;
if (!file) {
return null;
}
let lineSuffix = '';
const { start_line: startLine, end_line: endLine } = this.vulnerability.location;
if (startLine) {
lineSuffix += `:${startLine}`;
if (endLine && startLine !== endLine) {
lineSuffix += `-${endLine}`;
}
}
return `${file}${lineSuffix}`;
},
identifiers() {
return this.asNonEmptyListOrNull(this.vulnerability.identifiers);
},
className() {
return this.vulnerability.location?.class;
},
hasValue(field) {
return field.value && field.value.length > 0;
methodName() {
return this.vulnerability.location?.method;
},
hasInstances(key) {
return key === 'instances';
image() {
return this.vulnerability.location?.image;
},
hasIdentifiers(key) {
return key === 'identifiers';
namespace() {
return this.vulnerability.location?.operating_system;
},
hasLinks(key) {
return key === 'links';
links() {
return this.asNonEmptyListOrNull(this.vulnerability.links);
},
hasSeverity(key) {
return key === 'severity';
instances() {
return this.asNonEmptyListOrNull(this.vulnerability.instances);
},
},
methods: {
hasMoreValues(index, values) {
return index < values.length - 1;
},
asNonEmptyListOrNull(list) {
return list?.length > 0 ? list : null;
},
},
};
......@@ -43,13 +81,93 @@ export default {
<template>
<div class="border-white mb-0 px-3">
<div v-for="(field, key, index) in details" :key="index" class="d-sm-flex my-sm-2 my-4">
<label class="col-sm-2 text-sm-right font-weight-bold pl-0">{{ field.text }}:</label>
<div class="col-sm-10 pl-0 text-secondary">
<template v-if="hasValue(field)">
<div v-if="hasInstances(key)" class="info-well">
<vulnerability-detail
v-if="vulnerability.description"
:label="s__('Vulnerability|Description')"
>
<gl-friendly-wrap :text="vulnerability.description" />
</vulnerability-detail>
<vulnerability-detail v-if="vulnerability.project" :label="s__('Vulnerability|Project')">
<safe-link ref="projectLink" :href="vulnerability.project.full_path" target="_blank">
<gl-friendly-wrap :text="vulnerability.project.full_name" />
</safe-link>
</vulnerability-detail>
<vulnerability-detail v-if="url" :label="__('URL')">
<safe-link ref="urlLink" :href="url" target="_blank">
<gl-friendly-wrap :text="url" />
</safe-link>
</vulnerability-detail>
<vulnerability-detail v-if="file" :label="s__('Vulnerability|File')">
<safe-link
v-if="vulnerability.blob_path"
ref="fileLink"
:href="vulnerability.blob_path"
target="_blank"
>
<gl-friendly-wrap :text="file" />
</safe-link>
<gl-friendly-wrap v-else :text="file" />
</vulnerability-detail>
<vulnerability-detail v-if="identifiers" :label="s__('Vulnerability|Identifiers')">
<span v-for="(identifier, i) in identifiers" :key="i">
<safe-link
v-if="identifier.url"
ref="identifiersLink"
:href="identifier.url"
target="_blank"
rel="noopener noreferrer"
>
{{ identifier.name }}
</safe-link>
<span v-else> {{ identifier.name }} </span>
<span v-if="hasMoreValues(i, identifiers)">,&nbsp;</span>
</span>
</vulnerability-detail>
<vulnerability-detail v-if="vulnerability.severity" :label="s__('Vulnerability|Severity')">
<severity-badge :severity="vulnerability.severity" class="d-inline" />
</vulnerability-detail>
<vulnerability-detail
v-if="vulnerability.report_type"
:label="s__('Vulnerability|Report Type')"
>
<gl-friendly-wrap :text="vulnerability.report_type" />
</vulnerability-detail>
<vulnerability-detail v-if="className" :label="s__('Vulnerability|Class')">
<gl-friendly-wrap :text="className" />
</vulnerability-detail>
<vulnerability-detail v-if="methodName" :label="s__('Vulnerability|Method')">
<gl-friendly-wrap :text="methodName" />
</vulnerability-detail>
<vulnerability-detail v-if="image" :label="s__('Vulnerability|Image')">
<gl-friendly-wrap :text="image" />
</vulnerability-detail>
<vulnerability-detail v-if="namespace" :label="s__('Vulnerability|Namespace')">
<gl-friendly-wrap :text="namespace" />
</vulnerability-detail>
<vulnerability-detail v-if="links" :label="s__('Vulnerability|Links')">
<span v-for="(link, i) in links" :key="i">
<safe-link ref="linksLink" :href="link.url" target="_blank" rel="noopener noreferrer">
{{ link.value || link.url }}
</safe-link>
<span v-if="hasMoreValues(i, links)">,&nbsp;</span>
</span>
</vulnerability-detail>
<vulnerability-detail v-if="instances" :label="s__('Vulnerability|Instances')">
<div class="info-well">
<ul class="report-block-list">
<li v-for="(instance, i) in field.value" :key="i" class="report-block-list-issue">
<li v-for="(instance, i) in instances" :key="i" class="report-block-list-issue">
<div class="report-block-list-icon append-right-5 failed">
<icon :size="32" name="status_failed_borderless" />
</div>
......@@ -68,61 +186,17 @@ export default {
</safe-link>
</div>
<expand-button v-if="instance.evidence">
<template #expanded>
<pre
slot="expanded"
class="block report-block-dast-code prepend-top-10 report-block-issue-code"
>
{{ instance.evidence }}</pre
>
v-text="instance.evidence"
></pre>
</template>
</expand-button>
</div>
</li>
</ul>
</div>
<template v-else-if="hasIdentifiers(key)">
<span v-for="(identifier, i) in field.value" :key="i">
<safe-link
v-if="identifier.url"
:class="`js-link-${key}`"
:href="identifier.url"
target="_blank"
rel="noopener noreferrer"
>
{{ identifier.name }}
</safe-link>
<span v-else> {{ identifier.name }} </span>
<span v-if="hasMoreValues(i, field.value)">,&nbsp;</span>
</span>
</template>
<template v-else-if="hasLinks(key)">
<span v-for="(link, i) in field.value" :key="i">
<safe-link
:class="`js-link-${key}`"
:href="link.url"
target="_blank"
rel="noopener noreferrer"
>
{{ link.value || link.url }}
</safe-link>
<span v-if="hasMoreValues(i, field.value)">,&nbsp;</span>
</span>
</template>
<template v-else-if="hasSeverity(key)">
<severity-badge :severity="field.value" class="d-inline" />
</template>
<template v-else>
<safe-link
v-if="field.isLink"
:class="`js-link-${key}`"
:href="field.url"
target="_blank"
>
<gl-friendly-wrap :text="field.value" />
</safe-link>
<gl-friendly-wrap v-else :text="field.value" />
</template>
</template>
</div>
</div>
</vulnerability-detail>
</div>
</template>
import Vue from 'vue';
import * as types from './mutation_types';
import { findIssueIndex, parseDiff } from './utils';
import getFileLocation from './utils/get_file_location';
import { visitUrl } from '~/lib/utils/url_utility';
export default {
......@@ -134,36 +133,8 @@ export default {
[types.SET_ISSUE_MODAL_DATA](state, payload) {
const { issue, status } = payload;
const fileLocation = getFileLocation(issue.location);
Vue.set(state.modal, 'title', issue.title);
Vue.set(state.modal.data.description, 'value', issue.description);
Vue.set(state.modal.data.file, 'value', issue.location && issue.location.file);
Vue.set(state.modal.data.file, 'url', issue.urlPath);
Vue.set(state.modal.data.className, 'value', issue.location && issue.location.class);
Vue.set(state.modal.data.methodName, 'value', issue.location && issue.location.method);
Vue.set(state.modal.data.image, 'value', issue.location && issue.location.image);
Vue.set(state.modal.data.namespace, 'value', issue.location && issue.location.operating_system);
Vue.set(state.modal.data.url, 'value', fileLocation);
Vue.set(state.modal.data.url, 'url', fileLocation);
if (issue.identifiers && issue.identifiers.length > 0) {
Vue.set(state.modal.data.identifiers, 'value', issue.identifiers);
} else {
// Force a null value for identifiers to avoid showing an empty array
Vue.set(state.modal.data.identifiers, 'value', null);
}
Vue.set(state.modal.data.severity, 'value', issue.severity);
if (issue.links && issue.links.length > 0) {
Vue.set(state.modal.data.links, 'value', issue.links);
} else {
// Force a null value for links to avoid showing an empty array
Vue.set(state.modal.data.links, 'value', null);
}
Vue.set(state.modal.data.instances, 'value', issue.instances);
Vue.set(state.modal, 'vulnerability', issue);
Vue.set(state.modal, 'isResolved', status === 'success');
......
import { __, s__ } from '~/locale';
export default () => ({
blobPath: {
head: null,
......@@ -67,66 +65,6 @@ export default () => ({
modal: {
title: null,
// Dynamic data rendered for each issue
data: {
description: {
value: null,
text: s__('ciReport|Description'),
isLink: false,
},
url: {
value: null,
url: null,
text: __('URL'),
isLink: true,
},
file: {
value: null,
url: null,
text: s__('ciReport|File'),
isLink: true,
},
identifiers: {
value: [],
text: s__('ciReport|Identifiers'),
isLink: false,
},
severity: {
value: null,
text: s__('ciReport|Severity'),
isLink: false,
},
className: {
value: null,
text: s__('ciReport|Class'),
isLink: false,
},
methodName: {
value: null,
text: s__('ciReport|Method'),
isLink: false,
},
image: {
value: null,
text: s__('ciReport|Image'),
isLink: false,
},
namespace: {
value: null,
text: s__('ciReport|Namespace'),
isLink: false,
},
links: {
value: [],
text: s__('ciReport|Links'),
isLink: false,
},
instances: {
value: [],
text: s__('ciReport|Instances'),
isLink: false,
},
},
learnMoreUrl: null,
vulnerability: {
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`VulnerabilityDetail component renders the label prop and default slot 1`] = `
<div
class="d-sm-flex my-sm-2 my-4"
>
<label
class="col-sm-2 text-sm-right font-weight-bold pl-0"
>
foo:
</label>
<div
class="col-sm-10 pl-0 text-secondary"
>
<p>
bar
</p>
</div>
</div>
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`VulnerabilityDetails component pin test renders correctly 1`] = `
<div
class="border-white mb-0 px-3"
>
<vulnerability-detail-stub
label="Description"
>
<gl-friendly-wrap-stub
symbols="/"
text="The serialize-javascript npm package is vulnerable to Cross-site Scripting (XSS). It does not properly mitigate against unsafe characters in serialized regular expressions. If serialized data of regular expression objects are used in an environment other than Node.js, it is affected by this vulnerability."
/>
</vulnerability-detail-stub>
<vulnerability-detail-stub
label="Project"
>
<safe-link-stub
href="/gitlab-org/gitlab-ui"
target="_blank"
>
<gl-friendly-wrap-stub
symbols="/"
text="GitLab.org / gitlab-ui"
/>
</safe-link-stub>
</vulnerability-detail-stub>
<!---->
<vulnerability-detail-stub
label="File"
>
<safe-link-stub
href="/gitlab-org/gitlab-ui/blob/ad137f0a8ac59af961afe47d04e5cc062c6864a9/yarn.lock"
target="_blank"
>
<gl-friendly-wrap-stub
symbols="/"
text="yarn.lock"
/>
</safe-link-stub>
</vulnerability-detail-stub>
<vulnerability-detail-stub
label="Identifiers"
>
<span>
<safe-link-stub
href="https://deps.sec.gitlab.com/packages/npm/serialize-javascript/versions/1.7.0/advisories"
rel="noopener noreferrer"
target="_blank"
>
Gemnasium-58caa017-9a9a-46d6-bab2-ec930f46833c
</safe-link-stub>
<span>
</span>
</span>
<span>
<safe-link-stub
href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-16769"
rel="noopener noreferrer"
target="_blank"
>
CVE-2019-16769
</safe-link-stub>
<!---->
</span>
</vulnerability-detail-stub>
<vulnerability-detail-stub
label="Severity"
>
<severity-badge-stub
class="d-inline"
severity="unknown"
/>
</vulnerability-detail-stub>
<vulnerability-detail-stub
label="Report Type"
>
<gl-friendly-wrap-stub
symbols="/"
text="dependency_scanning"
/>
</vulnerability-detail-stub>
<!---->
<!---->
<!---->
<!---->
<vulnerability-detail-stub
label="Links"
>
<span>
<safe-link-stub
href="https://nvd.nist.gov/vuln/detail/CVE-2019-16769"
rel="noopener noreferrer"
target="_blank"
>
https://nvd.nist.gov/vuln/detail/CVE-2019-16769
</safe-link-stub>
<!---->
</span>
</vulnerability-detail-stub>
<vulnerability-detail-stub
label="Instances"
>
<div
class="info-well"
>
<ul
class="report-block-list"
>
<li
class="report-block-list-issue"
>
<div
class="report-block-list-icon append-right-5 failed"
>
<icon-stub
name="status_failed_borderless"
size="32"
/>
</div>
<div
class="report-block-list-issue-description prepend-top-5 append-bottom-5"
>
<div
class="report-block-list-issue-description-text"
>
POST
</div>
<div
class="report-block-list-issue-description-link"
>
<safe-link-stub
class="break-link"
href="/bar"
rel="noopener noreferrer nofollow"
target="_blank"
>
/bar
</safe-link-stub>
</div>
<expand-button-stub />
</div>
</li>
</ul>
</div>
</vulnerability-detail-stub>
</div>
`;
......@@ -286,8 +286,10 @@ describe('Security Reports modal', () => {
modal: createState().modal,
};
propsData.modal.vulnerability.blob_path = blobPath;
propsData.modal.data.namespace.value = namespaceValue;
propsData.modal.data.file.value = fileValue;
propsData.modal.vulnerability.location = {
file: fileValue,
operating_system: namespaceValue,
};
mountComponent({ propsData }, mount);
});
......@@ -295,23 +297,8 @@ describe('Security Reports modal', () => {
const vulnerabilityDetails = wrapper.find('.js-vulnerability-details');
expect(vulnerabilityDetails.exists()).toBe(true);
expect(vulnerabilityDetails.text()).toContain('foobar');
});
it('computes valued fields properly', () => {
expect(wrapper.vm.valuedFields).toMatchObject({
file: {
value: fileValue,
url: blobPath,
isLink: true,
text: 'File',
},
namespace: {
value: namespaceValue,
text: 'Namespace',
isLink: false,
},
});
expect(vulnerabilityDetails.text()).toContain(namespaceValue);
expect(vulnerabilityDetails.text()).toContain(fileValue);
});
});
......
import { shallowMount } from '@vue/test-utils';
import VulnerabilityDetail from 'ee/vue_shared/security_reports/components/vulnerability_detail.vue';
describe('VulnerabilityDetail component', () => {
let wrapper;
const factory = ({ propsData, defaultSlot }) => {
wrapper = shallowMount(VulnerabilityDetail, {
propsData,
slots: {
default: defaultSlot,
},
});
};
afterEach(() => {
wrapper.destroy();
});
it('renders the label prop and default slot', () => {
factory({ propsData: { label: 'foo' }, defaultSlot: '<p>bar</p>' });
expect(wrapper.element).toMatchSnapshot();
});
});
import { mount } from '@vue/test-utils';
import createState from 'ee/vue_shared/security_reports/store/state';
import { mount, shallowMount } from '@vue/test-utils';
import VulnerabilityDetails from 'ee/vue_shared/security_reports/components/vulnerability_details.vue';
import SeverityBadge from 'ee/vue_shared/security_reports/components/severity_badge.vue';
import SafeLink from 'ee/vue_shared/components/safe_link.vue';
import { TEST_HOST } from 'helpers/test_constants';
import { cloneDeep } from 'lodash';
import { mockFindings } from '../mock_data';
function makeVulnerability(changes = {}) {
return Object.assign(cloneDeep(mockFindings[0]), changes);
}
describe('VulnerabilityDetails component', () => {
let wrapper;
const componentFactory = (options = {}) => {
const componentFactory = vulnerability => {
wrapper = mount(VulnerabilityDetails, {
...options,
propsData: { vulnerability },
});
};
......@@ -20,57 +25,56 @@ describe('VulnerabilityDetails component', () => {
expect(link.text()).toBe(text);
};
const findLink = name => wrapper.find({ ref: `${name}Link` });
afterEach(() => {
wrapper.destroy();
});
it('renders severity with a badge', () => {
const value = 'critical';
componentFactory({ propsData: { details: { severity: { value } } } });
const vulnerability = makeVulnerability({ severity: 'critical' });
componentFactory(vulnerability);
const badge = wrapper.find(SeverityBadge);
expect(badge.props('severity')).toBe(value);
expect(badge.props('severity')).toBe(vulnerability.severity);
});
it('renders link fields with link', () => {
const details = {
somefield: {
value: 'foo',
url: `${TEST_HOST}/bar`,
isLink: true,
},
};
componentFactory({ propsData: { details } });
const vulnerability = makeVulnerability();
componentFactory(vulnerability);
expectSafeLink(wrapper.find('.js-link-somefield'), {
href: `${TEST_HOST}/bar`,
text: 'foo',
expectSafeLink(findLink('project'), {
href: vulnerability.project.full_path,
text: vulnerability.project.full_name,
});
});
it('renders wrapped file paths', () => {
const value = '/some/file/path';
const valueWrapped = '/<wbr>some/<wbr>file/<wbr>path';
const details = {
file: {
value,
url: `${TEST_HOST}/bar`,
isLink: true,
const vulnerability = makeVulnerability({
blob_path: `${TEST_HOST}/bar`,
location: {
file: '/some/file/path',
},
};
componentFactory({ propsData: { details } });
expect(wrapper.find(SafeLink).html()).toMatch(valueWrapped);
});
componentFactory(vulnerability);
expect(findLink('file').html()).toMatch('/<wbr>some/<wbr>file/<wbr>path');
});
it('escapes wrapped file paths', () => {
const value = '/unsafe/path<script></script>';
const valueWrapped = '/<wbr>unsafe/<wbr>path&lt;script&gt;&lt;/<wbr>script&gt;';
const details = {
file: {
value,
url: `${TEST_HOST}/bar`,
isLink: true,
const vulnerability = makeVulnerability({
blob_path: `${TEST_HOST}/bar`,
location: {
file: '/unsafe/path<script></script>',
},
};
componentFactory({ propsData: { details } });
expect(wrapper.find(SafeLink).html()).toMatch(valueWrapped);
});
componentFactory(vulnerability);
expect(findLink('file').html()).toMatch(
'/<wbr>unsafe/<wbr>path&lt;script&gt;&lt;/<wbr>script&gt;',
);
});
describe('does not render XSS links', () => {
......@@ -78,49 +82,35 @@ describe('VulnerabilityDetails component', () => {
const badUrl = 'javascript:alert("")';
beforeEach(() => {
const details = createState().modal.data;
details.file.value = 'badFile.lock';
details.file.url = badUrl;
details.links.value = [
{
url: badUrl,
},
];
details.identifiers.value = [
{
type: 'CVE',
name: 'BAD_URL',
url: badUrl,
},
];
details.instances.value = [
{
param: 'X-Content-Type-Options',
method: 'GET',
uri: badUrl,
const vulnerability = makeVulnerability({
blob_path: badUrl,
location: {
file: 'badFile.lock',
},
];
links: [{ url: badUrl }],
identifiers: [{ name: 'BAD_URL', url: badUrl }],
instances: [{ method: 'GET', uri: badUrl }],
});
componentFactory({ propsData: { details } });
componentFactory(vulnerability);
});
it('for the link field', () => {
expectSafeLink(wrapper.find('.js-link-links'), {
expectSafeLink(findLink('links'), {
href: badUrl,
text: badUrl,
});
});
it('for the identifiers field', () => {
expectSafeLink(wrapper.find('.js-link-identifiers'), {
expectSafeLink(findLink('identifiers'), {
href: badUrl,
text: 'BAD_URL',
});
});
it('for the file field', () => {
expectSafeLink(wrapper.find('.js-link-file'), {
expectSafeLink(findLink('file'), {
href: badUrl,
text: 'badFile.lock',
});
......@@ -136,16 +126,14 @@ describe('VulnerabilityDetails component', () => {
describe('with instances', () => {
beforeEach(() => {
const details = {
instances: {
value: [
const vulnerability = makeVulnerability({
instances: [
{ uri: 'http://192.168.32.236:3001/explore?sort=latest_activity_desc' },
{ uri: 'http://192.168.32.236:3001/help/user/group/subgroups/index.md' },
],
},
};
});
componentFactory({ propsData: { details } });
componentFactory(vulnerability);
});
it('renders instances list', () => {
......@@ -160,4 +148,30 @@ describe('VulnerabilityDetails component', () => {
);
});
});
describe('pin test', () => {
const factory = vulnFinding => {
wrapper = shallowMount(VulnerabilityDetails, {
propsData: {
vulnerability: vulnFinding,
},
});
};
it('renders correctly', () => {
factory(
makeVulnerability({
instances: [
{
method: 'POST',
evidence: 'foo',
uri: '/bar',
},
],
}),
);
expect(wrapper.element).toMatchSnapshot();
});
});
});
......@@ -97,51 +97,12 @@ describe('security reports mutations', () => {
describe('SET_ISSUE_MODAL_DATA', () => {
it('has default data', () => {
expect(stateCopy.modal.data.description.value).toEqual(null);
expect(stateCopy.modal.data.description.text).toEqual('Description');
expect(stateCopy.modal.data.description.isLink).toEqual(false);
expect(stateCopy.modal.data.identifiers.value).toEqual([]);
expect(stateCopy.modal.data.identifiers.text).toEqual('Identifiers');
expect(stateCopy.modal.data.identifiers.isLink).toEqual(false);
expect(stateCopy.modal.data.file.value).toEqual(null);
expect(stateCopy.modal.data.file.text).toEqual('File');
expect(stateCopy.modal.data.file.isLink).toEqual(true);
expect(stateCopy.modal.data.className.value).toEqual(null);
expect(stateCopy.modal.data.className.text).toEqual('Class');
expect(stateCopy.modal.data.className.isLink).toEqual(false);
expect(stateCopy.modal.data.methodName.value).toEqual(null);
expect(stateCopy.modal.data.methodName.text).toEqual('Method');
expect(stateCopy.modal.data.methodName.isLink).toEqual(false);
expect(stateCopy.modal.data.namespace.value).toEqual(null);
expect(stateCopy.modal.data.namespace.text).toEqual('Namespace');
expect(stateCopy.modal.data.namespace.isLink).toEqual(false);
expect(stateCopy.modal.data.severity.value).toEqual(null);
expect(stateCopy.modal.data.severity.text).toEqual('Severity');
expect(stateCopy.modal.data.severity.isLink).toEqual(false);
expect(stateCopy.modal.data.links.value).toEqual([]);
expect(stateCopy.modal.data.links.text).toEqual('Links');
expect(stateCopy.modal.data.links.isLink).toEqual(false);
expect(stateCopy.modal.data.instances.value).toEqual([]);
expect(stateCopy.modal.data.instances.text).toEqual('Instances');
expect(stateCopy.modal.data.instances.isLink).toEqual(false);
expect(stateCopy.modal.vulnerability.isDismissed).toEqual(false);
expect(stateCopy.modal.vulnerability.hasIssue).toEqual(false);
expect(stateCopy.modal.isCreatingNewIssue).toEqual(false);
expect(stateCopy.modal.isDismissingVulnerability).toEqual(false);
expect(stateCopy.modal.data.url.value).toEqual(null);
expect(stateCopy.modal.data.url.url).toEqual(null);
expect(stateCopy.modal.title).toEqual(null);
expect(stateCopy.modal.learnMoreUrl).toEqual(null);
expect(stateCopy.modal.error).toEqual(null);
......@@ -194,23 +155,6 @@ describe('security reports mutations', () => {
mutations[types.SET_ISSUE_MODAL_DATA](stateCopy, { issue, status });
expect(stateCopy.modal.title).toEqual(issue.title);
expect(stateCopy.modal.data.description.value).toEqual(issue.description);
expect(stateCopy.modal.data.file.value).toEqual(issue.location.file);
expect(stateCopy.modal.data.file.url).toEqual(issue.urlPath);
expect(stateCopy.modal.data.className.value).toEqual(issue.location.class);
expect(stateCopy.modal.data.methodName.value).toEqual(issue.location.method);
expect(stateCopy.modal.data.namespace.value).toEqual(issue.location.operating_system);
expect(stateCopy.modal.data.image.value).toEqual(issue.location.image);
expect(stateCopy.modal.data.identifiers.value).toEqual(issue.identifiers);
expect(stateCopy.modal.data.severity.value).toEqual(issue.severity);
expect(stateCopy.modal.data.links.value).toEqual(issue.links);
expect(stateCopy.modal.data.instances.value).toEqual(issue.instances);
expect(stateCopy.modal.data.url.value).toEqual(
`${issue.location.hostname}${issue.location.path}`,
);
expect(stateCopy.modal.data.url.url).toEqual(
`${issue.location.hostname}${issue.location.path}`,
);
expect(stateCopy.modal.vulnerability).toEqual(issue);
expect(stateCopy.modal.isResolved).toEqual(true);
});
......
......@@ -37,7 +37,7 @@ describe('vulnerabilities module mutations', () => {
mutations[types.SET_VULNERABILITIES_ENDPOINT](state, endpoint);
expect(state.vulnerabilitiesEndpoint).toEqual(endpoint);
expect(state.vulnerabilitiesEndpoint).toBe(endpoint);
});
});
......@@ -46,7 +46,7 @@ describe('vulnerabilities module mutations', () => {
it(`should set pageInfo.page to ${page}`, () => {
mutations[types.SET_VULNERABILITIES_PAGE](state, page);
expect(state.pageInfo.page).toEqual(page);
expect(state.pageInfo.page).toBe(page);
});
});
......@@ -116,7 +116,7 @@ describe('vulnerabilities module mutations', () => {
mutations[types.SET_VULNERABILITIES_COUNT_ENDPOINT](state, endpoint);
expect(state.vulnerabilitiesCountEndpoint).toEqual(endpoint);
expect(state.vulnerabilitiesCountEndpoint).toBe(endpoint);
});
});
......@@ -166,7 +166,7 @@ describe('vulnerabilities module mutations', () => {
mutations[types.SET_VULNERABILITIES_HISTORY_ENDPOINT](state, endpoint);
expect(state.vulnerabilitiesHistoryEndpoint).toEqual(endpoint);
expect(state.vulnerabilitiesHistoryEndpoint).toBe(endpoint);
});
});
......@@ -214,19 +214,19 @@ describe('vulnerabilities module mutations', () => {
it('should set the vulnerabilitiesHistoryDayRange to number of days', () => {
mutations[types.SET_VULNERABILITIES_HISTORY_DAY_RANGE](state, DAYS.THIRTY);
expect(state.vulnerabilitiesHistoryDayRange).toEqual(DAYS.THIRTY);
expect(state.vulnerabilitiesHistoryDayRange).toBe(DAYS.THIRTY);
});
it('should set the vulnerabilitiesHistoryMaxDayInterval to 7 if days are 60 and under', () => {
mutations[types.SET_VULNERABILITIES_HISTORY_DAY_RANGE](state, DAYS.THIRTY);
expect(state.vulnerabilitiesHistoryMaxDayInterval).toEqual(7);
expect(state.vulnerabilitiesHistoryMaxDayInterval).toBe(7);
});
it('should set the vulnerabilitiesHistoryMaxDayInterval to 14 if over 60', () => {
mutations[types.SET_VULNERABILITIES_HISTORY_DAY_RANGE](state, DAYS.NINETY);
expect(state.vulnerabilitiesHistoryMaxDayInterval).toEqual(14);
expect(state.vulnerabilitiesHistoryMaxDayInterval).toBe(14);
});
});
......@@ -241,68 +241,17 @@ describe('vulnerabilities module mutations', () => {
});
it('should set the modal title', () => {
expect(state.modal.title).toEqual(vulnerability.name);
});
it('should set the modal description', () => {
expect(state.modal.data.description.value).toEqual(vulnerability.description);
expect(state.modal.title).toBe(vulnerability.name);
});
it('should set the modal project', () => {
expect(state.modal.data.project.value).toEqual(vulnerability.project.full_name);
expect(state.modal.data.project.url).toEqual(vulnerability.project.full_path);
});
it('should set the modal file', () => {
expect(state.modal.data.file.value).toEqual(
`${vulnerability.location.file}:${vulnerability.location.start_line}`,
);
});
it('should set the modal className', () => {
expect(state.modal.data.className.value).toEqual(vulnerability.location.class);
});
it('should set the modal image', () => {
expect(state.modal.data.image.value).toEqual(vulnerability.location.image);
});
it('should set the modal namespace', () => {
expect(state.modal.data.namespace.value).toEqual(vulnerability.location.operating_system);
});
it('should set the modal identifiers', () => {
expect(state.modal.data.identifiers.value).toEqual(vulnerability.identifiers);
});
it('should set the modal severity', () => {
expect(state.modal.data.severity.value).toEqual(vulnerability.severity);
});
it('should set the modal class', () => {
expect(state.modal.data.className.value).toEqual(vulnerability.location.class);
});
it('should set the modal links', () => {
expect(state.modal.data.links.value).toEqual(vulnerability.links);
});
it('should set the modal instances', () => {
expect(state.modal.data.instances.value).toEqual(vulnerability.instances);
expect(state.modal.project.value).toBe(vulnerability.project.full_name);
expect(state.modal.project.url).toBe(vulnerability.project.full_path);
});
it('should set the modal vulnerability', () => {
expect(state.modal.vulnerability).toEqual(vulnerability);
});
it('should set the modal URL', () => {
const { url } = state.modal.data;
const { hostname, path } = vulnerability.location;
const expected = `${hostname}${path}`;
expect(url.value).toEqual(expected);
expect(url.url).toEqual(expected);
});
});
describe('with irregular data', () => {
......@@ -313,7 +262,7 @@ describe('vulnerabilities module mutations', () => {
};
mutations[types.SET_MODAL_DATA](state, payload);
expect(state.modal.vulnerability.isDismissed).toEqual(true);
expect(state.modal.vulnerability.isDismissed).toBe(true);
});
it('should set hasIssue when the vulnerabilitiy has a related issue', () => {
......@@ -327,7 +276,7 @@ describe('vulnerabilities module mutations', () => {
};
mutations[types.SET_MODAL_DATA](state, payload);
expect(state.modal.vulnerability.hasIssue).toEqual(true);
expect(state.modal.vulnerability.hasIssue).toBe(true);
});
it('should not set hasIssue when the issue_iid in null', () => {
......@@ -341,28 +290,7 @@ describe('vulnerabilities module mutations', () => {
};
mutations[types.SET_MODAL_DATA](state, payload);
expect(state.modal.vulnerability.hasIssue).toEqual(false);
});
it('should nullify the modal links', () => {
const payload = { vulnerability: { ...vulnerability, links: [] } };
mutations[types.SET_MODAL_DATA](state, payload);
expect(state.modal.data.links.value).toEqual(null);
});
it('should nullify the instances', () => {
const payload = { vulnerability: { ...vulnerability, instances: [] } };
mutations[types.SET_MODAL_DATA](state, payload);
expect(state.modal.data.instances.value).toEqual(null);
});
it('should nullify the file value', () => {
const payload = { vulnerability: { ...vulnerability, location: {} } };
mutations[types.SET_MODAL_DATA](state, payload);
expect(state.modal.data.file.value).toEqual(null);
expect(state.modal.vulnerability.hasIssue).toBe(false);
});
});
});
......@@ -409,7 +337,7 @@ describe('vulnerabilities module mutations', () => {
});
it('should set the error state on the modal', () => {
expect(state.modal.error).toEqual('There was an error creating the issue');
expect(state.modal.error).toBe('There was an error creating the issue');
});
});
......@@ -455,7 +383,7 @@ describe('vulnerabilities module mutations', () => {
});
it('should set the error state on the modal', () => {
expect(state.modal.error).toEqual('There was an error creating the merge request');
expect(state.modal.error).toBe('There was an error creating the merge request');
});
});
......@@ -521,7 +449,7 @@ describe('vulnerabilities module mutations', () => {
});
it('should set the error state on the modal', () => {
expect(state.modal.error).toEqual('There was an error dismissing the vulnerability.');
expect(state.modal.error).toBe('There was an error dismissing the vulnerability.');
});
});
......@@ -587,7 +515,7 @@ describe('vulnerabilities module mutations', () => {
});
it('should set the error state on the modal', () => {
expect(state.modal.error).toEqual('There was an error deleting the comment.');
expect(state.modal.error).toBe('There was an error deleting the comment.');
});
});
......@@ -673,7 +601,7 @@ describe('vulnerabilities module mutations', () => {
});
it('should set the error state on the modal', () => {
expect(state.modal.error).toEqual('There was an error adding the comment.');
expect(state.modal.error).toBe('There was an error adding the comment.');
});
});
......@@ -737,7 +665,7 @@ describe('vulnerabilities module mutations', () => {
});
it('should set the error state on the modal', () => {
expect(state.modal.error).toEqual('There was an error reverting the dismissal.');
expect(state.modal.error).toBe('There was an error reverting the dismissal.');
});
});
......
......@@ -21817,6 +21817,9 @@ msgstr ""
msgid "Vulnerability|Links"
msgstr ""
msgid "Vulnerability|Method"
msgstr ""
msgid "Vulnerability|Namespace"
msgstr ""
......@@ -22935,9 +22938,6 @@ msgstr ""
msgid "ciReport|Base pipeline codequality artifact not found"
msgstr ""
msgid "ciReport|Class"
msgstr ""
msgid "ciReport|Code quality"
msgstr ""
......@@ -22968,9 +22968,6 @@ msgstr ""
msgid "ciReport|Dependency scanning"
msgstr ""
msgid "ciReport|Description"
msgstr ""
msgid "ciReport|Download patch to resolve"
msgstr ""
......@@ -22983,42 +22980,21 @@ msgstr ""
msgid "ciReport|Failed to load %{reportName} report"
msgstr ""
msgid "ciReport|File"
msgstr ""
msgid "ciReport|Fixed:"
msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
msgid "ciReport|Image"
msgstr ""
msgid "ciReport|Instances"
msgstr ""
msgid "ciReport|Investigate this vulnerability by creating an issue"
msgstr ""
msgid "ciReport|Learn more about interacting with security reports"
msgstr ""
msgid "ciReport|Links"
msgstr ""
msgid "ciReport|Loading %{reportName} report"
msgstr ""
msgid "ciReport|Manage licenses"
msgstr ""
msgid "ciReport|Method"
msgstr ""
msgid "ciReport|Namespace"
msgstr ""
msgid "ciReport|No changes to code quality"
msgstr ""
......@@ -23040,9 +23016,6 @@ msgstr ""
msgid "ciReport|Security scanning failed loading any results"
msgstr ""
msgid "ciReport|Severity"
msgstr ""
msgid "ciReport|Solution"
msgstr ""
......
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