Commit 18d13bf7 authored by Mark Florian's avatar Mark Florian Committed by Phil Hughes

Extract `vulnerability-details`-specific tests

These new tests run in jest. An additional test was added to the
existing karma-based modal test suite to ensure the new component is
rendered.
parent 63fe6fec
......@@ -2,26 +2,22 @@
import { s__ } from '~/locale';
import Modal from '~/vue_shared/components/gl_modal.vue';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import Icon from '~/vue_shared/components/icon.vue';
import ExpandButton from '~/vue_shared/components/expand_button.vue';
import EventItem from 'ee/vue_shared/security_reports/components/event_item.vue';
import SafeLink from 'ee/vue_shared/components/safe_link.vue';
import SolutionCard from 'ee/vue_shared/security_reports/components/solution_card.vue';
import SeverityBadge from './severity_badge.vue';
import SplitButton from 'ee/vue_shared/security_reports/components/split_button.vue';
import VulnerabilityDetails from 'ee/vue_shared/security_reports/components/vulnerability_details.vue';
export default {
components: {
EventItem,
ExpandButton,
Icon,
LoadingButton,
Modal,
SafeLink,
SeverityBadge,
SolutionCard,
SplitButton,
VulnerabilityDetails,
},
props: {
modal: {
......@@ -136,24 +132,6 @@ export default {
this.$emit('dismissIssue');
}
},
isLastValue(index, values) {
return index < values.length - 1;
},
hasValue(field) {
return field.value && field.value.length > 0;
},
hasInstances(field, key) {
return key === 'instances' && this.hasValue(field);
},
hasIdentifiers(field, key) {
return key === 'identifiers' && this.hasValue(field);
},
hasLinks(field, key) {
return key === 'links' && this.hasValue(field);
},
hasSeverity(field, key) {
return key === 'severity' && this.hasValue(field);
},
},
};
</script>
......@@ -165,89 +143,7 @@ export default {
class="modal-security-report-dast"
>
<slot>
<div class="border-white mb-0 px-3">
<div v-for="(field, key, index) in valuedFields" :key="index" class="d-flex my-2">
<label class="col-2 text-right font-weight-bold pl-0">{{ field.text }}:</label>
<div class="col-10 pl-0 text-secondary">
<div v-if="hasInstances(field, key)" class="info-well">
<ul class="report-block-list">
<li v-for="(instance, i) in field.value" :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>
<div class="report-block-list-issue-description prepend-top-5 append-bottom-5">
<div class="report-block-list-issue-description-text">
{{ instance.method }}
</div>
<div class="report-block-list-issue-description-link">
<safe-link
:href="instance.uri"
target="_blank"
rel="noopener noreferrer nofollow"
class="break-link"
>
{{ instance.uri }}
</safe-link>
</div>
<expand-button v-if="instance.evidence">
<pre
slot="expanded"
class="block report-block-dast-code prepend-top-10 report-block-issue-code"
>
{{ instance.evidence }}</pre
>
</expand-button>
</div>
</li>
</ul>
</div>
<template v-else-if="hasIdentifiers(field, 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="isLastValue(i, field.value)">,&nbsp;</span>
</span>
</template>
<template v-else-if="hasLinks(field, 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="isLastValue(i, field.value)">,&nbsp;</span>
</span>
</template>
<template v-else-if="hasSeverity(field, 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"
>
{{ field.value }}
</safe-link>
<span v-else :class="{ 'text-capitalize': key === 'confidence' }">
{{ field.value }}
</span>
</template>
</div>
</div>
</div>
<vulnerability-details :details="valuedFields" class="js-vulnerability-details" />
<solution-card v-if="renderSolutionCard" :solution="solution" :remediation="remediation" />
<hr v-else />
......
<script>
import Icon from '~/vue_shared/components/icon.vue';
import SafeLink from 'ee/vue_shared/components/safe_link.vue';
import SeverityBadge from './severity_badge.vue';
export default {
name: 'VulnerabilityDetails',
components: {
Icon,
SafeLink,
SeverityBadge,
},
props: {
details: {
type: Object,
required: true,
},
},
methods: {
hasMoreValues(index, values) {
return index < values.length - 1;
},
hasValue(field) {
return field.value && field.value.length > 0;
},
hasInstances(field, key) {
return key === 'instances' && this.hasValue(field);
},
hasIdentifiers(field, key) {
return key === 'identifiers' && this.hasValue(field);
},
hasLinks(field, key) {
return key === 'links' && this.hasValue(field);
},
hasSeverity(field, key) {
return key === 'severity' && this.hasValue(field);
},
},
};
</script>
<template>
<div class="border-white mb-0 px-3">
<div v-for="(field, key, index) in details" :key="index" class="d-flex my-2">
<label class="col-2 text-right font-weight-bold pl-0">{{ field.text }}:</label>
<div class="col-10 pl-0 text-secondary">
<div v-if="hasInstances(field, key)" class="info-well">
<ul class="report-block-list">
<li v-for="(instance, i) in field.value" :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>
<div class="report-block-list-issue-description prepend-top-5 append-bottom-5">
<div class="report-block-list-issue-description-text">
{{ instance.method }}
</div>
<div class="report-block-list-issue-description-link">
<safe-link
:href="instance.uri"
target="_blank"
rel="noopener noreferrer nofollow"
class="break-link"
>
{{ instance.uri }}
</safe-link>
</div>
<expand-button v-if="instance.evidence">
<pre
slot="expanded"
class="block report-block-dast-code prepend-top-10 report-block-issue-code"
>
{{ instance.evidence }}</pre
>
</expand-button>
</div>
</li>
</ul>
</div>
<template v-else-if="hasIdentifiers(field, 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(field, 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(field, 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"
>
{{ field.value }}
</safe-link>
<span v-else :class="{ 'text-capitalize': key === 'confidence' }">
{{ field.value }}
</span>
</template>
</div>
</div>
</div>
</template>
import { createLocalVue, shallowMount } from '@vue/test-utils';
import createState from 'ee/vue_shared/security_reports/store/state';
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';
describe('VulnerabilityDetails component', () => {
let wrapper;
const localVue = createLocalVue();
const componentFactory = (options = {}) => {
wrapper = shallowMount(localVue.extend(VulnerabilityDetails), {
...options,
localVue,
sync: false,
});
};
const expectSafeLink = (link, { href, text }) => {
expect(link.is(SafeLink)).toBe(true);
expect(link.props('href')).toBe(href);
expect(link.text()).toBe(text);
};
it('renders severity with a badge', () => {
const value = 'critical';
componentFactory({ propsData: { details: { severity: { value } } } });
const badge = wrapper.find(SeverityBadge);
expect(badge.props('severity')).toBe(value);
});
it('renders link fields with link', () => {
const details = {
somefield: {
value: 'foo',
url: `${TEST_HOST}/bar`,
isLink: true,
},
};
componentFactory({ propsData: { details } });
expectSafeLink(wrapper.find('.js-link-somefield'), {
href: `${TEST_HOST}/bar`,
text: 'foo',
});
});
describe('does not render XSS links', () => {
// eslint-disable-next-line no-script-url
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,
},
];
componentFactory({ propsData: { details } });
});
it('for the link field', () => {
expectSafeLink(wrapper.find('.js-link-links'), {
href: badUrl,
text: badUrl,
});
});
it('for the identifiers field', () => {
expectSafeLink(wrapper.find('.js-link-identifiers'), {
href: badUrl,
text: 'BAD_URL',
});
});
it('for the file field', () => {
expectSafeLink(wrapper.find('.js-link-file'), {
href: badUrl,
text: 'badFile.lock',
});
});
it('for the instances field', () => {
expectSafeLink(wrapper.find('.report-block-list-issue-description-link .break-link'), {
href: badUrl,
text: badUrl,
});
});
});
describe('with instances', () => {
beforeEach(() => {
const details = {
instances: {
value: [
{ 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 } });
});
it('renders instances list', () => {
const instances = wrapper.findAll('.report-block-list li').wrappers;
expect(instances[0].text()).toContain(
'http://192.168.32.236:3001/explore?sort=latest_activity_desc',
);
expect(instances[1].text()).toContain(
'http://192.168.32.236:3001/help/user/group/subgroups/index.md',
);
});
});
});
......@@ -2,8 +2,6 @@ import Vue from 'vue';
import component from 'ee/vue_shared/security_reports/components/modal.vue';
import createState from 'ee/vue_shared/security_reports/store/state';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { trimText } from 'spec/helpers/vue_component_helper';
import { TEST_HOST } from 'spec/test_constants';
describe('Security Reports modal', () => {
const Component = Vue.extend(component);
......@@ -115,31 +113,6 @@ describe('Security Reports modal', () => {
});
});
describe('with instances', () => {
beforeEach(() => {
const props = {
modal: createState().modal,
};
props.modal.data.instances.value = [
{ 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' },
];
vm = mountComponent(Component, props);
});
it('renders instances list', () => {
const instances = vm.$el.querySelectorAll('.report-block-list li');
expect(instances[0].textContent).toContain(
'http://192.168.32.236:3001/explore?sort=latest_activity_desc',
);
expect(instances[1].textContent).toContain(
'http://192.168.32.236:3001/help/user/group/subgroups/index.md',
);
});
});
describe('data', () => {
beforeEach(() => {
const props = {
......@@ -147,33 +120,18 @@ describe('Security Reports modal', () => {
vulnerabilityFeedbackHelpPath: 'feedbacksHelpPath',
};
props.modal.title = 'Arbitrary file existence disclosure in Action Pack';
props.modal.data.file.value = 'Gemfile.lock';
props.modal.data.file.url = `${TEST_HOST}/path/Gemfile.lock`;
props.modal.data.severity = { value: 'critical' };
vm = mountComponent(Component, props);
});
it('renders keys in `data`', () => {
it('renders title', () => {
expect(vm.$el.textContent).toContain('Arbitrary file existence disclosure in Action Pack');
});
it('renders link fields with link', () => {
expect(vm.$el.querySelector('.js-link-file').getAttribute('href')).toEqual(
`${TEST_HOST}/path/Gemfile.lock`,
);
});
it('renders help link', () => {
expect(
vm.$el.querySelector('.js-link-vulnerabilityFeedbackHelpPath').getAttribute('href'),
).toEqual('feedbacksHelpPath');
});
it('renders severity with a badge', () => {
const badge = vm.$el.querySelector('.severity-badge');
expect(badge.textContent).toContain('Critical');
});
});
});
......@@ -209,6 +167,21 @@ describe('Security Reports modal', () => {
});
});
describe('Vulnerability Details', () => {
it('is rendered', () => {
const props = {
modal: createState().modal,
};
props.modal.data.namespace.value = 'foobar';
vm = mountComponent(Component, props);
const vulnerabilityDetails = vm.$el.querySelector('.js-vulnerability-details');
expect(vulnerabilityDetails).not.toBeNull();
expect(vulnerabilityDetails.textContent).toContain('foobar');
});
});
describe('Solution Card', () => {
it('is rendered if the vulnerability has a solution', () => {
const props = {
......@@ -253,67 +226,4 @@ describe('Security Reports modal', () => {
expect(vm.$el.querySelector('hr')).not.toBeNull();
});
});
describe('does not render XSS links', () => {
// eslint-disable-next-line no-script-url
const badUrl = 'javascript:alert("")';
beforeEach(() => {
const props = {
modal: createState().modal,
};
props.modal.data.file.value = 'badFile.lock';
props.modal.data.file.url = badUrl;
props.modal.data.links.value = [
{
url: badUrl,
},
];
props.modal.data.identifiers.value = [
{
type: 'CVE',
name: 'BAD_URL',
url: badUrl,
},
];
props.modal.data.instances.value = [
{
param: 'X-Content-Type-Options',
method: 'GET',
uri: badUrl,
},
];
vm = mountComponent(Component, props);
});
it('for the link field', () => {
const linkEl = vm.$el.querySelector('.js-link-links');
expect(linkEl.tagName).not.toBe('A');
expect(trimText(linkEl.textContent)).toBe(badUrl);
});
it('for the identifiers field', () => {
const linkEl = vm.$el.querySelector('.js-link-identifiers');
expect(linkEl.tagName).not.toBe('A');
expect(trimText(linkEl.textContent)).toBe('BAD_URL');
});
it('for the file field', () => {
const linkEl = vm.$el.querySelector('.js-link-file');
expect(linkEl.tagName).not.toBe('A');
expect(trimText(linkEl.textContent)).toBe('badFile.lock');
});
it('for the instances field', () => {
const linkEl = vm.$el.querySelector('.report-block-list-issue-description-link .break-link');
expect(linkEl.tagName).not.toBe('A');
expect(trimText(linkEl.textContent)).toBe(badUrl);
});
});
});
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