Commit aa2d6c24 authored by Phil Hughes's avatar Phil Hughes

Merge branch '4310-security-reports-step-1' into 'master'

Break security issues report component into smaller ones

See merge request gitlab-org/gitlab-ee!5038
parents cac6a815 0a742530
......@@ -2,6 +2,7 @@
import ReportSection from 'ee/vue_shared/security_reports/components/report_section.vue';
import securityMixin from 'ee/vue_shared/security_reports/mixins/security_report_mixin';
import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import { SAST } from 'ee/vue_shared/security_reports/helpers/constants';
export default {
name: 'SecurityReportTab',
......@@ -9,9 +10,8 @@
LoadingIcon,
ReportSection,
},
mixins: [
securityMixin,
],
mixins: [securityMixin],
sast: SAST,
props: {
securityReports: {
type: Object,
......@@ -24,7 +24,7 @@
<div class="pipeline-tab-content">
<report-section
class="js-sast-widget"
type="security"
:type="$options.sast"
:status="checkReportStatus(securityReports.sast.isLoading, securityReports.sast.hasError)"
:loading-text="translateText('security').loading"
:error-text="translateText('security').error"
......@@ -32,7 +32,6 @@
:unresolved-issues="securityReports.sast.newIssues"
:resolved-issues="securityReports.sast.resolvedIssues"
:all-issues="securityReports.sast.allIssues"
:has-priority="true"
:is-collapsible="false"
/>
</div>
......
<script>
/**
* Renders Code quality body text
* Fixed: [name] in [link]:[line]
*/
import ReportLink from 'ee/vue_shared/security_reports/components/report_link.vue';
export default {
name: 'CodequalityIssueBody',
components: {
ReportLink,
},
props: {
isStatusSuccess: {
type: Boolean,
required: true,
},
issue: {
type: Object,
required: true,
},
},
};
</script>
<template>
<div class="report-block-list-issue-description prepend-top-5 append-bottom-5">
<div class="report-block-list-issue-description-text append-right-5">
<template v-if="isStatusSuccess">{{ s__('ciReport|Fixed:') }}</template>
{{ issue.name }}
</div>
<report-link
v-if="issue.path"
:issue="issue"
/>
</div>
</template>
<script>
/**
* Renders Perfomance issue body text
* [name] :[score] [symbol] [delta] in [link]
*/
import ReportLink from 'ee/vue_shared/security_reports/components/report_link.vue';
export default {
name: 'PerformanceIssueBody',
components: {
ReportLink,
},
props: {
issue: {
type: Object,
required: true,
},
},
methods: {
formatScore(value) {
if (Math.floor(value) !== value) {
return parseFloat(value).toFixed(2);
}
return value;
},
},
};
</script>
<template>
<div class="report-block-list-issue-description prepend-top-5 append-bottom-5">
<div class="report-block-list-issue-description-text append-right-5">
{{ issue.name }}<template v-if="issue.score">:
<strong>{{ formatScore(issue.score) }}</strong></template>
<template v-if="issue.delta != null">
({{ issue.delta >= 0 ? '+' : '' }}{{ formatScore(issue.delta) }})
</template>
</div>
<report-link
v-if="issue.path"
:issue="issue"
/>
</div>
</template>
......@@ -4,6 +4,11 @@ import WidgetApprovals from './components/approvals/mr_widget_approvals';
import GeoSecondaryNode from './components/states/mr_widget_secondary_geo_node';
import ReportSection from '../vue_shared/security_reports/components/report_section.vue';
import securityMixin from '../vue_shared/security_reports/mixins/security_report_mixin';
import {
SAST,
DAST,
SAST_CONTAINER,
} from '../vue_shared/security_reports/helpers/constants';
export default {
extends: CEWidgetOptions,
......@@ -15,6 +20,9 @@ export default {
mixins: [
securityMixin,
],
dast: DAST,
sast: SAST,
sastContainer: SAST_CONTAINER,
data() {
return {
isLoadingCodequality: false,
......@@ -318,7 +326,7 @@ export default {
:success-text="codequalityText"
:unresolved-issues="mr.codeclimateMetrics.newIssues"
:resolved-issues="mr.codeclimateMetrics.resolvedIssues"
/>
/>
<report-section
class="js-performance-widget"
v-if="shouldRenderPerformance"
......@@ -330,11 +338,11 @@ export default {
:unresolved-issues="mr.performanceMetrics.degraded"
:resolved-issues="mr.performanceMetrics.improved"
:neutral-issues="mr.performanceMetrics.neutral"
/>
/>
<report-section
class="js-sast-widget"
v-if="shouldRenderSecurityReport"
type="security"
:type="$options.sast"
:status="securityStatus"
:loading-text="translateText('security').loading"
:error-text="translateText('security').error"
......@@ -342,12 +350,11 @@ export default {
:unresolved-issues="mr.securityReport.newIssues"
:resolved-issues="mr.securityReport.resolvedIssues"
:all-issues="mr.securityReport.allIssues"
:has-priority="true"
/>
<report-section
class="js-docker-widget"
v-if="shouldRenderDockerReport"
type="docker"
:type="$options.sastContainer"
:status="dockerStatus"
:loading-text="translateText('sast:container').loading"
:error-text="translateText('sast:container').error"
......@@ -355,19 +362,17 @@ export default {
:unresolved-issues="mr.dockerReport.unapproved"
:neutral-issues="mr.dockerReport.approved"
:info-text="sastContainerInformationText()"
:has-priority="true"
/>
/>
<report-section
class="js-dast-widget"
v-if="shouldRenderDastReport"
type="dast"
:type="$options.dast"
:status="dastStatus"
:loading-text="translateText('DAST').loading"
:error-text="translateText('DAST').error"
:success-text="getDastText"
:unresolved-issues="mr.dastReport"
:has-priority="true"
/>
/>
<div class="mr-widget-section">
<component
:is="componentName"
......
<script>
/**
* Renders DAST body text
* [priority]: [name]
*/
export default {
name: 'SastIssueBody',
props: {
issue: {
type: Object,
required: true,
},
issueIndex: {
type: Number,
required: true,
},
modalTargetId: {
type: String,
required: true,
},
},
methods: {
openDastModal() {
this.$emit('openDastModal', this.issue, this.issueIndex);
},
},
};
</script>
<template>
<div class="report-block-list-issue-description prepend-top-5 append-bottom-5">
<div class="report-block-list-issue-description-text append-right-5">
<template v-if="issue.priority">{{ issue.priority }}:</template>
<button
type="button"
@click="openDastModal()"
data-toggle="modal"
class="js-modal-dast btn-link btn-blank text-left break-link"
:data-target="modalTargetId"
>
{{ issue.name }}
</button>
</div>
</div>
</template>
<script>
import $ from 'jquery';
import { s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import Modal from '~/vue_shared/components/gl_modal.vue';
import ExpandButton from '~/vue_shared/components/expand_button.vue';
import PerformanceIssue from 'ee/vue_merge_request_widget/components/performance_issue_body.vue';
import CodequalityIssue from 'ee/vue_merge_request_widget/components/codequality_issue_body.vue';
import SastIssue from './sast_issue_body.vue';
import SastContainerIssue from './sast_container_issue_body.vue';
import DastIssue from './dast_issue_body.vue';
import { SAST, DAST, SAST_CONTAINER } from '../helpers/constants';
const modalDefaultData = {
modalId: 'modal-mrwidget-issue',
......@@ -19,6 +25,11 @@
Modal,
Icon,
ExpandButton,
SastIssue,
SastContainerIssue,
DastIssue,
PerformanceIssue,
CodequalityIssue,
},
props: {
issues: {
......@@ -35,19 +46,11 @@
type: String,
required: true,
},
hasPriority: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return modalDefaultData;
},
computed: {
fixedLabel() {
return s__('ciReport|Fixed:');
},
iconName() {
if (this.isStatusFailed) {
return 'status_failed_borderless';
......@@ -66,20 +69,20 @@
isStatusNeutral() {
return this.status === 'neutral';
},
isTypeQuality() {
isTypeCodequality() {
return this.type === 'codequality';
},
isTypePerformance() {
return this.type === 'performance';
},
isTypeSecurity() {
return this.type === 'security';
isTypeSast() {
return this.type === SAST;
},
isTypeDocker() {
return this.type === 'docker';
isTypeSastContainer() {
return this.type === SAST_CONTAINER;
},
isTypeDast() {
return this.type === 'dast';
return this.type === DAST;
},
},
mounted() {
......@@ -88,21 +91,12 @@
});
},
methods: {
shouldRenderPriority(issue) {
return this.hasPriority && issue.priority;
},
getmodalId(index) {
return `modal-mrwidget-issue-${index}`;
},
modalIdTarget(index) {
return `#${this.getmodalId(index)}`;
},
formatScore(value) {
if (Math.floor(value) !== value) {
return parseFloat(value).toFixed(2);
}
return value;
},
openDastModal(issue, index) {
this.modalId = this.getmodalId(index);
this.modalTitle = `${issue.priority}: ${issue.name}`;
......@@ -145,61 +139,35 @@
:size="32"
/>
</div>
<div class="report-block-list-issue-description prepend-top-5 append-bottom-5">
<div class="report-block-list-issue-description-text append-right-5">
<template v-if="isStatusSuccess && isTypeQuality">{{ fixedLabel }}</template>
<template v-if="shouldRenderPriority(issue)">{{ issue.priority }}:</template>
<template v-if="isTypeDocker">
<a
v-if="issue.nameLink"
:href="issue.nameLink"
target="_blank"
rel="noopener noreferrer nofollow"
>{{ issue.name }}</a>
<template v-else>
{{ issue.name }}
</template>
</template>
<template v-else-if="isTypeDast">
<button
type="button"
@click="openDastModal(issue, index)"
data-toggle="modal"
class="js-modal-dast btn-link btn-blank text-left break-link"
:data-target="modalTargetId"
>
{{ issue.name }}
</button>
</template>
<template v-else>
{{ issue.name }}<template v-if="issue.score">:
<strong>{{ formatScore(issue.score) }}</strong></template>
</template>
<sast-issue
v-if="isTypeSast"
:issue="issue"
/>
<template v-if="isTypePerformance && issue.delta != null">
({{ issue.delta >= 0 ? '+' : '' }}{{ formatScore(issue.delta) }})
</template>
</div>
<div class="report-block-list-issue-description-link">
<template v-if="issue.path">
in
<dast-issue
v-else-if="isTypeDast"
:issue="issue"
:issue-index="index"
:modal-target-id="modalTargetId"
@openDastModal="openDastModal"
/>
<a
v-if="issue.urlPath"
:href="issue.urlPath"
target="_blank"
rel="noopener noreferrer nofollow"
class="break-link"
>
{{ issue.path }}<template v-if="issue.line">:{{ issue.line }}</template>
</a>
<template v-else>
{{ issue.path }}<template v-if="issue.line">:{{ issue.line }}</template>
</template>
</template>
</div>
</div>
<sast-container-issue
v-else-if="isTypeSastContainer"
:issue="issue"
/>
<codequality-issue
v-else-if="isTypeCodequality"
:is-status-success="isStatusSuccess"
:issue="issue"
/>
<performance-issue
v-else-if="isTypePerformance"
:issue="issue"
/>
</li>
</ul>
......
<script>
export default {
name: 'ReportIssueLink',
props: {
issue: {
type: Object,
required: true,
},
},
};
</script>
<template>
<div class="report-block-list-issue-description-link">
in
<a
v-if="issue.urlPath"
:href="issue.urlPath"
target="_blank"
rel="noopener noreferrer nofollow"
class="break-link"
>
{{ issue.path }}<template v-if="issue.line">:{{ issue.line }}</template>
</a>
<template v-else>
{{ issue.path }}<template v-if="issue.line">:{{ issue.line }}</template>
</template>
</div>
</template>
......@@ -178,7 +178,7 @@
>
<p
v-if="type === 'docker' && infoText"
v-if="infoText"
v-html="infoText"
class="js-mr-code-quality-info prepend-left-10 report-block-info"
>
......
<script>
/**
* Renders SAST CONTAINER body text
* [priority]: [name|link] in [link]:[line]
*/
import ReportLink from './report_link.vue';
export default {
name: 'SastContainerIssueBody',
components: {
ReportLink,
},
props: {
issue: {
type: Object,
required: true,
},
},
};
</script>
<template>
<div class="report-block-list-issue-description prepend-top-5 append-bottom-5">
<div class="report-block-list-issue-description-text append-right-5">
<template v-if="issue.priority">{{ issue.priority }}:</template>
<a
v-if="issue.nameLink"
:href="issue.nameLink"
target="_blank"
rel="noopener noreferrer nofollow"
>
{{ issue.name }}
</a>
<template v-else>
{{ issue.name }}
</template>
</div>
<report-link
v-if="issue.path"
:issue="issue"
/>
</div>
</template>
<script>
/**
* Renders SAST body text
* [priority]: [name] in [link] : [line]
*/
import ReportLink from './report_link.vue';
export default {
name: 'SastIssueBody',
components: {
ReportLink,
},
props: {
issue: {
type: Object,
required: true,
},
},
};
</script>
<template>
<div class="report-block-list-issue-description prepend-top-5 append-bottom-5">
<div class="report-block-list-issue-description-text append-right-5">
<template v-if="issue.priority">{{ issue.priority }}:</template>
{{ issue.name }}
</div>
<report-link
v-if="issue.path"
:issue="issue"
/>
</div>
</template>
export const SAST = 'SAST';
export const DAST = 'DAST';
export const SAST_CONTAINER = 'SAST_CONTAINER';
import Vue from 'vue';
import component from 'ee/vue_merge_request_widget/components/codequality_issue_body.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
describe('sast issue body', () => {
let vm;
const Component = Vue.extend(component);
const codequalityIssue = {
name:
'rubygem-rest-client: session fixation vulnerability via Set-Cookie headers in 30x redirection responses',
path: 'Gemfile.lock',
severity: 'normal',
type: 'Issue',
urlPath: '/Gemfile.lock#L22',
};
afterEach(() => {
vm.$destroy();
});
describe('with success', () => {
it('renders fixed label', () => {
vm = mountComponent(Component, {
issue: codequalityIssue,
isStatusSuccess: true,
});
expect(vm.$el.textContent.trim()).toContain('Fixed');
});
});
describe('without success', () => {
it('renders fixed label', () => {
vm = mountComponent(Component, {
issue: codequalityIssue,
isStatusSuccess: false,
});
expect(vm.$el.textContent.trim()).not.toContain('Fixed');
});
});
describe('name', () => {
it('renders name', () => {
vm = mountComponent(Component, {
issue: codequalityIssue,
isStatusSuccess: false,
});
expect(vm.$el.textContent.trim()).toContain(codequalityIssue.name);
});
});
describe('path', () => {
it('renders name', () => {
vm = mountComponent(Component, {
issue: codequalityIssue,
isStatusSuccess: false,
});
expect(vm.$el.querySelector('a').getAttribute('href')).toEqual(
codequalityIssue.urlPath,
);
expect(vm.$el.querySelector('a').textContent.trim()).toEqual(
codequalityIssue.path,
);
});
});
});
import Vue from 'vue';
import component from 'ee/vue_merge_request_widget/components/performance_issue_body.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
describe('performance issue body', () => {
let vm;
const Component = Vue.extend(component);
const performanceIssue = {
delta: 0.1999999999998181,
name: 'Transfer Size (KB)',
path: '/',
score: 4974.8,
};
afterEach(() => {
vm.$destroy();
});
beforeEach(() => {
vm = mountComponent(Component, {
issue: performanceIssue,
});
});
it('renders issue name', () => {
expect(vm.$el.textContent.trim()).toContain(performanceIssue.name);
});
it('renders issue score formatted', () => {
expect(vm.$el.textContent.trim()).toContain('4974.80');
});
it('renders issue delta formatted', () => {
expect(vm.$el.textContent.trim()).toContain('(+0.20)');
});
});
import Vue from 'vue';
import component from 'ee/vue_shared/security_reports/components/dast_issue_body.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
describe('dast issue body', () => {
let vm;
const Component = Vue.extend(component);
const dastIssue = {
alert: 'X-Content-Type-Options Header Missing',
confidence: '2',
count: '17',
cweid: '16',
desc:
'<p>The Anti-MIME-Sniffing header X-Content-Type-Options was not set to "nosniff". </p>',
name: 'X-Content-Type-Options Header Missing',
parsedDescription:
' The Anti-MIME-Sniffing header X-Content-Type-Options was not set to "nosniff". ',
priority: 'Low (Medium)',
reference:
'<p>http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx</p><p>https://www.owasp.org/index.php/List_of_useful_HTTP_headers</p>',
riskcode: '1',
riskdesc: 'Low (Medium)',
};
afterEach(() => {
vm.$destroy();
});
describe('with priority', () => {
it('renders priority key', () => {
vm = mountComponent(Component, {
issue: dastIssue,
issueIndex: 1,
modalTargetId: '#modal-mrwidget-issue',
});
expect(vm.$el.textContent.trim()).toContain(dastIssue.priority);
});
});
describe('without priority', () => {
it('does not rendere priority key', () => {
const issueCopy = Object.assign({}, dastIssue);
delete issueCopy.priority;
vm = mountComponent(Component, {
issue: issueCopy,
issueIndex: 1,
modalTargetId: '#modal-mrwidget-issue',
});
expect(vm.$el.textContent.trim()).not.toContain(dastIssue.priority);
});
});
describe('issue name', () => {
beforeEach(() => {
vm = mountComponent(Component, {
issue: dastIssue,
issueIndex: 1,
modalTargetId: '#modal-mrwidget-issue',
});
});
it('renders issue name', () => {
expect(vm.$el.textContent.trim()).toContain(dastIssue.name);
});
it('renders button to open modal box', () => {
const button = vm.$el.querySelector('.js-modal-dast');
expect(button.getAttribute('data-toggle')).toEqual('modal');
expect(button.getAttribute('data-target')).toEqual('#modal-mrwidget-issue');
});
it('emits event when button is clicked', () => {
spyOn(vm, '$emit');
vm.$el.querySelector('.js-modal-dast').click();
expect(vm.$emit).toHaveBeenCalledWith('openDastModal', dastIssue, 1);
});
});
});
......@@ -67,26 +67,21 @@ describe('Report issues', () => {
beforeEach(() => {
vm = mountComponent(ReportIssues, {
issues: sastParsedIssues,
type: 'security',
type: 'SAST',
status: 'failed',
hasPriority: true,
});
});
it('should render a list of unresolved issues', () => {
expect(vm.$el.querySelectorAll('.report-block-list li').length).toEqual(sastParsedIssues.length);
});
it('should render priority', () => {
expect(vm.$el.querySelector('.report-block-list li').textContent).toContain(sastParsedIssues[0].priority);
});
});
describe('with location', () => {
it('should render location', () => {
vm = mountComponent(ReportIssues, {
issues: sastParsedIssues,
type: 'security',
type: 'SAST',
status: 'failed',
});
......@@ -101,7 +96,7 @@ describe('Report issues', () => {
issues: [{
name: 'foo',
}],
type: 'security',
type: 'SAST',
status: 'failed',
});
......@@ -114,9 +109,8 @@ describe('Report issues', () => {
beforeEach(() => {
vm = mountComponent(ReportIssues, {
issues: dockerReportParsed.unapproved,
type: 'docker',
type: 'SAST_CONTAINER',
status: 'failed',
hasPriority: true,
});
});
......@@ -149,9 +143,8 @@ describe('Report issues', () => {
beforeEach(() => {
vm = mountComponent(ReportIssues, {
issues: parsedDast,
type: 'dast',
type: 'DAST',
status: 'failed',
hasPriority: true,
});
});
......
import Vue from 'vue';
import component from 'ee/vue_shared/security_reports/components/report_link.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
describe('report link', () => {
let vm;
const Component = Vue.extend(component);
afterEach(() => {
vm.$destroy();
});
describe('With url', () => {
it('renders link', () => {
vm = mountComponent(Component, {
issue: {
path: 'Gemfile.lock',
urlPath: '/Gemfile.lock',
},
});
expect(vm.$el.textContent.trim()).toContain('in');
expect(vm.$el.querySelector('a').getAttribute('href')).toEqual('/Gemfile.lock');
expect(vm.$el.querySelector('a').textContent.trim()).toEqual('Gemfile.lock');
});
});
describe('Without url', () => {
it('does not render link', () => {
vm = mountComponent(Component, {
issue: {
path: 'Gemfile.lock',
},
});
expect(vm.$el.querySelector('a')).toBeNull();
expect(vm.$el.textContent.trim()).toContain('in');
expect(vm.$el.textContent.trim()).toContain('Gemfile.lock');
});
});
describe('with line', () => {
it('renders line number', () => {
vm = mountComponent(Component, {
issue: {
path: 'Gemfile.lock',
urlPath:
'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00',
line: 22,
},
});
expect(vm.$el.querySelector('a').textContent.trim()).toContain('Gemfile.lock:22');
});
});
describe('without line', () => {
it('does not render line number', () => {
vm = mountComponent(Component, {
issue: {
path: 'Gemfile.lock',
urlPath:
'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00',
},
});
expect(vm.$el.querySelector('a').textContent.trim()).not.toContain(':22');
});
});
});
......@@ -104,7 +104,7 @@ describe('Report section', () => {
vm = mountComponent(ReportSection, {
status: 'success',
successText: 'SAST improved on 1 security vulnerability and degraded on 1 security vulnerability',
type: 'security',
type: 'SAST',
errorText: 'Failed to load security report',
hasPriority: true,
loadingText: 'Loading security report',
......
import Vue from 'vue';
import component from 'ee/vue_shared/security_reports/components/sast_container_issue_body.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
describe('sast container issue body', () => {
let vm;
const Component = Vue.extend(component);
const sastContainerIssue = {
name: 'CVE-2017-11671',
nameLink: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-11671',
namespace: 'debian:8',
path: 'debian:8',
priority: 'Low',
severity: 'Low',
vulnerability: 'CVE-2017-11671',
};
afterEach(() => {
vm.$destroy();
});
describe('with priority', () => {
it('renders priority key', () => {
vm = mountComponent(Component, {
issue: sastContainerIssue,
});
expect(vm.$el.textContent.trim()).toContain(sastContainerIssue.priority);
});
});
describe('without priority', () => {
it('does not rendere priority key', () => {
const issueCopy = Object.assign({}, sastContainerIssue);
delete issueCopy.priority;
vm = mountComponent(Component, {
issue: issueCopy,
});
expect(vm.$el.textContent.trim()).not.toContain(sastContainerIssue.priority);
});
});
describe('with name link', () => {
it('renders name link', () => {
vm = mountComponent(Component, {
issue: sastContainerIssue,
});
expect(vm.$el.querySelector('a').getAttribute('href')).toEqual(sastContainerIssue.nameLink);
expect(vm.$el.querySelector('a').textContent.trim()).toEqual(sastContainerIssue.name);
});
});
describe('without name link', () => {
it('does not render name link', () => {
const issueCopy = Object.assign({}, sastContainerIssue);
delete issueCopy.nameLink;
vm = mountComponent(Component, {
issue: issueCopy,
});
expect(vm.$el.querySelector('a')).toBeNull();
expect(vm.$el.textContent.trim()).toContain(sastContainerIssue.name);
});
});
describe('path', () => {
it('renders path', () => {
vm = mountComponent(Component, {
issue: sastContainerIssue,
});
expect(vm.$el.textContent.trim()).toContain(sastContainerIssue.path);
});
});
});
import Vue from 'vue';
import component from 'ee/vue_shared/security_reports/components/sast_issue_body.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
describe('sast issue body', () => {
let vm;
const Component = Vue.extend(component);
const sastIssue = {
cve: 'CVE-2016-9999',
file: 'Gemfile.lock',
message: 'Test Information Leak Vulnerability in Action View',
name: 'Test Information Leak Vulnerability in Action View',
path: 'Gemfile.lock',
solution:
'upgrade to >= 5.0.0.beta1.1, >= 4.2.5.1, ~> 4.2.5, >= 4.1.14.1, ~> 4.1.14, ~> 3.2.22.1',
tool: 'bundler_audit',
url:
'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00',
urlPath: '/Gemfile.lock',
priority: 'Low',
};
afterEach(() => {
vm.$destroy();
});
describe('with priority', () => {
it('renders priority key', () => {
vm = mountComponent(Component, {
issue: sastIssue,
});
expect(vm.$el.textContent.trim()).toContain(sastIssue.priority);
});
});
describe('without priority', () => {
it('does not rendere priority key', () => {
const issueCopy = Object.assign({}, sastIssue);
delete issueCopy.priority;
vm = mountComponent(Component, {
issue: issueCopy,
});
expect(vm.$el.textContent.trim()).not.toContain(
sastIssue.priority,
);
});
});
describe('name', () => {
it('renders name', () => {
vm = mountComponent(Component, {
issue: sastIssue,
});
expect(vm.$el.textContent.trim()).toContain(
sastIssue.name,
);
});
});
describe('path', () => {
it('renders name', () => {
vm = mountComponent(Component, {
issue: sastIssue,
});
expect(vm.$el.querySelector('a').getAttribute('href')).toEqual(
sastIssue.urlPath,
);
expect(vm.$el.querySelector('a').textContent.trim()).toEqual(
sastIssue.path,
);
});
});
});
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