Commit 8e193a3a authored by Yevgeny Name's avatar Yevgeny Name

Add Coverage Fuzzing to DB & Security Dashboard

This adds frontend

Related MR https://gitlab.com/gitlab-org/gitlab/-/merge_requests/34648
Backend MR https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37173
parent 497620d5
......@@ -237,7 +237,13 @@ export default {
};
},
},
securityReportTypes: ['dast', 'sast', 'dependencyScanning', 'containerScanning'],
securityReportTypes: [
'dast',
'sast',
'dependencyScanning',
'containerScanning',
'coverageFuzzing',
],
};
</script>
<template>
......@@ -311,6 +317,7 @@ export default {
:enabled-reports="mr.enabledReports"
:sast-help-path="mr.sastHelp"
:dast-help-path="mr.dastHelp"
:coverage-fuzzing-help-path="mr.coverageFuzzingHelp"
:container-scanning-help-path="mr.containerScanningHelp"
:dependency-scanning-help-path="mr.dependencyScanningHelp"
:secret-scanning-help-path="mr.secretScanningHelp"
......
......@@ -10,6 +10,7 @@ export default class MergeRequestStore extends CEMergeRequestStore {
this.sastHelp = data.sast_help_path;
this.containerScanningHelp = data.container_scanning_help_path;
this.dastHelp = data.dast_help_path;
this.coverageFuzzingHelp = data.coverage_fuzzing_help_path;
this.secretScanningHelp = data.secret_scanning_help_path;
this.dependencyScanningHelp = data.dependency_scanning_help_path;
this.vulnerabilityFeedbackPath = data.vulnerability_feedback_path;
......
......@@ -55,9 +55,6 @@ export default {
crashAddress() {
return this.vulnerability.location?.crash_address;
},
crashState() {
return this.vulnerability.location?.crash_state;
},
className() {
return this.vulnerability.location?.class;
},
......@@ -108,7 +105,7 @@ export default {
});
},
stacktraceSnippet() {
return this.vulnerability.stacktrace_snippet;
return this.vulnerability.location?.stacktrace_snippet;
},
},
methods: {
......@@ -182,10 +179,6 @@ export default {
<gl-friendly-wrap ref="crashAddress" :text="crashAddress" />
</vulnerability-detail>
<vulnerability-detail v-if="crashState" :label="s__('Crash State')">
<code-block ref="crashState" :code="crashState" max-height="225px" />
</vulnerability-detail>
<vulnerability-detail v-if="stacktraceSnippet" :label="s__('Stacktrace snippet')">
<code-block ref="stacktraceSnippet" :code="stacktraceSnippet" max-height="225px" />
</vulnerability-detail>
......
......@@ -92,6 +92,11 @@ export default {
required: false,
default: '',
},
coverageFuzzingHelpPath: {
type: String,
required: false,
default: '',
},
dependencyScanningHelpPath: {
type: String,
required: false,
......@@ -178,6 +183,7 @@ export default {
'sast',
'containerScanning',
'dast',
'coverageFuzzing',
'dependencyScanning',
'secretScanning',
'summaryCounts',
......@@ -193,10 +199,12 @@ export default {
'groupedDastText',
'groupedDependencyText',
'groupedSecretScanningText',
'groupedCoverageFuzzingText',
'containerScanningStatusIcon',
'dastStatusIcon',
'dependencyScanningStatusIcon',
'secretScanningStatusIcon',
'coverageFuzzingStatusIcon',
'isBaseSecurityReportOutOfDate',
'canCreateIssue',
'canCreateMergeRequest',
......@@ -215,6 +223,9 @@ export default {
hasDastReports() {
return this.enabledReports.dast;
},
hasCoverageFuzzingReports() {
return this.enabledReports.coverageFuzzing;
},
hasSastReports() {
return this.enabledReports.sast;
},
......@@ -239,6 +250,9 @@ export default {
dastDownloadLink() {
return this.dastSummary?.scannedResourcesCsvPath || '';
},
coverageFuzzingShowIssues() {
return this.coverageFuzzing.newIssues || this.coverageFuzzing.resolvedIssues;
},
},
created() {
......@@ -289,6 +303,13 @@ export default {
this.setSecretScanningDiffEndpoint(secretScanningDiffEndpoint);
this.fetchSecretScanningDiff();
}
const coverageFuzzingDiffEndpoint = gl?.mrWidgetData?.coverage_fuzzing_comparison_path;
if (coverageFuzzingDiffEndpoint && this.hasCoverageFuzzingReports) {
this.setCoverageFuzzingDiffEndpoint(coverageFuzzingDiffEndpoint);
this.fetchCoverageFuzzingDiff();
}
},
methods: {
...mapActions([
......@@ -322,6 +343,8 @@ export default {
'setDastDiffEndpoint',
'fetchSecretScanningDiff',
'setSecretScanningDiffEndpoint',
'fetchCoverageFuzzingDiff',
'setCoverageFuzzingDiffEndpoint',
]),
...mapActions('sast', {
setSastDiffEndpoint: 'setDiffEndpoint',
......@@ -514,6 +537,29 @@ export default {
/>
</template>
<template v-if="hasCoverageFuzzingReports">
<summary-row
:summary="groupedCoverageFuzzingText"
:status-icon="coverageFuzzingStatusIcon"
:popover-options="coverageFuzzingPopover"
class="js-coverage-fuzzing-widget"
data-qa-selector="coverage_fuzzing_report"
>
<template #summary>
<security-summary :message="groupedCoverageFuzzingText" />
</template>
</summary-row>
<grouped-issues-list
v-if="coverageFuzzingShowIssues"
:unresolved-issues="coverageFuzzing.newIssues"
:resolved-issues="coverageFuzzing.resolvedIssues"
:component="$options.componentNames.SecurityIssueBody"
class="report-block-group-list"
data-testid="coverage-fuzzing-issues-list"
/>
</template>
<issue-modal
:modal="modal"
:vulnerability-feedback-help-path="vulnerabilityFeedbackHelpPath"
......
......@@ -85,5 +85,18 @@ export default {
),
};
},
coverageFuzzingPopover() {
return {
title: s__('ciReport|Coverage Fuzzing Title'),
content: sprintf(
s__('ciReport|%{linkStartTag}Learn more about Coverage Fuzzing %{linkEndTag}'),
{
linkStartTag: getLinkStartTag(this.coverageFuzzingHelpPath),
linkEndTag,
},
false,
),
};
},
},
};
......@@ -145,6 +145,46 @@ export const fetchDependencyScanningDiff = ({ state, dispatch }) => {
export const updateDependencyScanningIssue = ({ commit }, issue) =>
commit(types.UPDATE_DEPENDENCY_SCANNING_ISSUE, issue);
/**
* COVERAGE FUZZING
*/
export const setCoverageFuzzingDiffEndpoint = ({ commit }, path) =>
commit(types.SET_COVERAGE_FUZZING_DIFF_ENDPOINT, path);
export const requestCoverageFuzzingDiff = ({ commit }) =>
commit(types.REQUEST_COVERAGE_FUZZING_DIFF);
export const receiveCoverageFuzzingDiffSuccess = ({ commit }, response) =>
commit(types.RECEIVE_COVERAGE_FUZZING_DIFF_SUCCESS, response);
export const receiveCoverageFuzzingDiffError = ({ commit }) =>
commit(types.RECEIVE_COVERAGE_FUZZING_DIFF_ERROR);
export const fetchCoverageFuzzingDiff = ({ state, dispatch }) => {
dispatch('requestCoverageFuzzingDiff');
return Promise.all([
pollUntilComplete(state.coverageFuzzing.paths.diffEndpoint),
axios.get(state.vulnerabilityFeedbackPath, {
params: {
category: 'coverage_fuzzing',
},
}),
])
.then(values => {
dispatch('receiveCoverageFuzzingDiffSuccess', {
diff: values[0].data,
enrichData: values[1].data,
});
})
.catch(() => {
dispatch('receiveCoverageFuzzingDiffError');
});
};
export const updateCoverageFuzzingIssue = ({ commit }, issue) =>
commit(types.UPDATE_COVERAGE_FUZZING_ISSUE, issue);
/**
* SECRET SCANNING
*/
......
......@@ -30,6 +30,14 @@ export const groupedDependencyText = ({ dependencyScanning }) =>
messages.DEPENDENCY_SCANNING_IS_LOADING,
);
export const groupedCoverageFuzzingText = ({ coverageFuzzing }) =>
groupedReportText(
coverageFuzzing,
messages.COVERAGE_FUZZING,
messages.COVERAGE_FUZZING_HAS_ERROR,
messages.COVERAGE_FUZZING_IS_LOADING,
);
export const summaryCounts = ({
containerScanning,
dast,
......@@ -107,6 +115,9 @@ export const dependencyScanningStatusIcon = ({ dependencyScanning }) =>
export const secretScanningStatusIcon = ({ secretScanning }) =>
statusIcon(secretScanning.isLoading, secretScanning.hasError, secretScanning.newIssues.length);
export const coverageFuzzingStatusIcon = ({ coverageFuzzing }) =>
statusIcon(coverageFuzzing.isLoading, coverageFuzzing.hasError, coverageFuzzing.newIssues.length);
export const areReportsLoading = state =>
state.sast.isLoading ||
state.dast.isLoading ||
......
......@@ -6,6 +6,7 @@ const updateIssueActionsMap = {
container_scanning: 'updateContainerScanningIssue',
dast: 'updateDastIssue',
secret_scanning: 'updateSecretScanningIssue',
coverage_fuzzing: 'updateCoverageFuzzingIssue',
};
export default function configureMediator(store) {
......
......@@ -8,6 +8,7 @@ const DAST = s__('ciReport|DAST');
const CONTAINER_SCANNING = s__('ciReport|Container scanning');
const DEPENDENCY_SCANNING = s__('ciReport|Dependency scanning');
const SECRET_SCANNING = s__('ciReport|Secret scanning');
const COVERAGE_FUZZING = s__('ciReport|Coverage fuzzing');
export default {
SAST,
......@@ -15,6 +16,7 @@ export default {
CONTAINER_SCANNING,
DEPENDENCY_SCANNING,
SECRET_SCANNING,
COVERAGE_FUZZING,
TRANSLATION_IS_LOADING,
TRANSLATION_HAS_ERROR,
SAST_IS_LOADING: sprintf(TRANSLATION_IS_LOADING, { reportType: SAST }),
......@@ -35,4 +37,8 @@ export default {
reportType: SECRET_SCANNING,
}),
SECRET_SCANNING_HAS_ERROR: sprintf(TRANSLATION_HAS_ERROR, { reportType: SECRET_SCANNING }),
COVERAGE_FUZZING_IS_LOADING: sprintf(TRANSLATION_IS_LOADING, {
reportType: COVERAGE_FUZZING,
}),
COVERAGE_FUZZING_HAS_ERROR: sprintf(TRANSLATION_HAS_ERROR, { reportType: COVERAGE_FUZZING }),
};
......@@ -36,6 +36,12 @@ export const REQUEST_SECRET_SCANNING_DIFF = 'REQUEST_SECRET_SCANNING_DIFF';
export const RECEIVE_SECRET_SCANNING_DIFF_SUCCESS = 'RECEIVE_SECRET_SCANNING_DIFF_SUCCESS';
export const RECEIVE_SECRET_SCANNING_DIFF_ERROR = 'RECEIVE_SECRET_SCANNING_DIFF_ERROR';
// COVERAGE FUZZING
export const SET_COVERAGE_FUZZING_DIFF_ENDPOINT = 'SET_COVERAGE_FUZZING_DIFF_ENDPOINT';
export const REQUEST_COVERAGE_FUZZING_DIFF = 'REQUEST_COVERAGE_FUZZING_DIFF';
export const RECEIVE_COVERAGE_FUZZING_DIFF_SUCCESS = 'RECEIVE_COVERAGE_FUZZING_DIFF_SUCCESS';
export const RECEIVE_COVERAGE_FUZZING_DIFF_ERROR = 'RECEIVE_COVERAGE_FUZZING_DIFF_ERROR';
// Dismiss security issue
export const SET_ISSUE_MODAL_DATA = 'SET_ISSUE_MODAL_DATA';
export const REQUEST_DISMISS_VULNERABILITY = 'REQUEST_DISMISS_VULNERABILITY';
......@@ -64,6 +70,7 @@ export const UPDATE_DEPENDENCY_SCANNING_ISSUE = 'UPDATE_DEPENDENCY_SCANNING_ISSU
export const UPDATE_CONTAINER_SCANNING_ISSUE = 'UPDATE_CONTAINER_SCANNING_ISSUE';
export const UPDATE_DAST_ISSUE = 'UPDATE_DAST_ISSUE';
export const UPDATE_SECRET_SCANNING_ISSUE = 'UPDATE_SECRET_SCANNING_ISSUE';
export const UPDATE_COVERAGE_FUZZING_ISSUE = 'UPDATE_COVERAGE_FUZZING_ISSUE';
export const OPEN_DISMISSAL_COMMENT_BOX = 'OPEN_DISMISSAL_COMMENT_BOX ';
export const CLOSE_DISMISSAL_COMMENT_BOX = 'CLOSE_DISMISSAL_COMMENT_BOX';
......@@ -101,6 +101,36 @@ export default {
Vue.set(state.dast, 'hasError', true);
},
// COVERAGE_FUZZING
[types.SET_COVERAGE_FUZZING_DIFF_ENDPOINT](state, path) {
Vue.set(state.coverageFuzzing.paths, 'diffEndpoint', path);
},
[types.REQUEST_COVERAGE_FUZZING_DIFF](state) {
Vue.set(state.coverageFuzzing, 'isLoading', true);
},
[types.RECEIVE_COVERAGE_FUZZING_DIFF_SUCCESS](state, { diff, enrichData }) {
const { added, fixed, existing } = parseDiff(diff, enrichData);
const baseReportOutofDate = diff.base_report_out_of_date || false;
const scans = diff.scans || [];
const hasBaseReport = Boolean(diff.base_report_created_at);
Vue.set(state.coverageFuzzing, 'isLoading', false);
Vue.set(state.coverageFuzzing, 'newIssues', added);
Vue.set(state.coverageFuzzing, 'resolvedIssues', fixed);
Vue.set(state.coverageFuzzing, 'allIssues', existing);
Vue.set(state.coverageFuzzing, 'baseReportOutofDate', baseReportOutofDate);
Vue.set(state.coverageFuzzing, 'hasBaseReport', hasBaseReport);
Vue.set(state.coverageFuzzing, 'scans', scans);
},
[types.RECEIVE_COVERAGE_FUZZING_DIFF_ERROR](state) {
Vue.set(state.coverageFuzzing, 'isLoading', false);
Vue.set(state.coverageFuzzing, 'hasError', true);
},
// DEPENDECY SCANNING
[types.SET_DEPENDENCY_SCANNING_DIFF_ENDPOINT](state, path) {
......
......@@ -44,7 +44,22 @@ export default () => ({
hasBaseReport: false,
scans: [],
},
coverageFuzzing: {
paths: {
head: null,
base: null,
diffEndpoint: null,
},
isLoading: false,
hasError: false,
newIssues: [],
resolvedIssues: [],
allIssues: [],
baseReportOutofDate: false,
hasBaseReport: false,
},
dependencyScanning: {
paths: {
head: null,
......
......@@ -18,6 +18,9 @@ export default {
location() {
return this.vulnerability.location || {};
},
stacktraceSnippet() {
return this.vulnerability.stacktrace_snippet || '';
},
scanner() {
return this.vulnerability.scanner || {};
},
......@@ -86,6 +89,11 @@ export default {
},
].filter(x => x.content);
},
shouldShowLocation() {
return (
this.location.crash_address || this.location.crash_type || this.location.stacktrace_snippet
);
},
},
methods: {
getHeadersAsCodeBlockLines(headers) {
......@@ -154,6 +162,23 @@ export default {
</detail-item>
</ul>
<template v-if="shouldShowLocation">
<h3>{{ __('Location') }}</h3>
<ul>
<detail-item
v-if="location.crash_address"
:sprintf-message="__('%{labelStart}Crash Address:%{labelEnd} %{crash_address}')"
>{{ location.crash_address }}
</detail-item>
<detail-item
v-if="location.stacktrace_snippet"
:sprintf-message="__('%{labelStart}Crash State:%{labelEnd} %{stacktrace_snippet}')"
>
<code-block :code="location.stacktrace_snippet" max-height="225px" />
</detail-item>
</ul>
</template>
<template v-if="location.file">
<h3>{{ __('Location') }}</h3>
<ul>
......
---
title: add add coverage fuzzing results to security dashboard
merge_request: 36011
author:
type: added
......@@ -23,6 +23,7 @@ import {
containerScanningDiffSuccessMock,
dependencyScanningDiffSuccessMock,
secretScanningDiffSuccessMock,
coverageFuzzingDiffSuccessMock,
} from 'ee_jest/vue_shared/security_reports/mock_data';
const SAST_SELECTOR = '.js-sast-widget';
......@@ -30,6 +31,7 @@ const DAST_SELECTOR = '.js-dast-widget';
const DEPENDENCY_SCANNING_SELECTOR = '.js-dependency-scanning-widget';
const CONTAINER_SCANNING_SELECTOR = '.js-container-scanning';
const SECRET_SCANNING_SELECTOR = '.js-secret-scanning';
const COVERAGE_FUZZING_SELECTOR = '.js-coverage-fuzzing-widget';
describe('ee merge request widget options', () => {
let vm;
......@@ -742,6 +744,78 @@ describe('ee merge request widget options', () => {
});
});
describe('Coverage Fuzzing', () => {
const COVERAGE_FUZZING_ENDPOINT = 'coverage_fuzzing_report';
beforeEach(() => {
gl.mrWidgetData = {
...mockData,
enabled_reports: {
coverage_fuzzing: true,
},
coverage_fuzzing_comparison_path: COVERAGE_FUZZING_ENDPOINT,
vulnerability_feedback_path: VULNERABILITY_FEEDBACK_ENDPOINT,
};
});
describe('when it is loading', () => {
it('should render loading indicator', () => {
mock.onGet(COVERAGE_FUZZING_ENDPOINT).reply(200, coverageFuzzingDiffSuccessMock);
mock.onGet(VULNERABILITY_FEEDBACK_ENDPOINT).reply(200, []);
vm = mountComponent(Component, { mrData: gl.mrWidgetData });
expect(
findSecurityWidget()
.querySelector(COVERAGE_FUZZING_SELECTOR)
.textContent.trim(),
).toContain('Coverage fuzzing is loading');
});
});
describe('with successful request', () => {
beforeEach(() => {
mock.onGet(COVERAGE_FUZZING_ENDPOINT).reply(200, coverageFuzzingDiffSuccessMock);
mock.onGet(VULNERABILITY_FEEDBACK_ENDPOINT).reply(200, []);
vm = mountComponent(Component, { mrData: gl.mrWidgetData });
});
it('should render provided data', done => {
setImmediate(() => {
expect(
findSecurityWidget()
.querySelector(`${COVERAGE_FUZZING_SELECTOR} .report-block-list-issue-description`)
.textContent.trim(),
).toEqual(
'Coverage fuzzing detected 1 new critical and 1 new high severity vulnerabilities.',
);
done();
});
});
});
describe('with failed request', () => {
beforeEach(() => {
mock.onGet(COVERAGE_FUZZING_ENDPOINT).reply(500, {});
mock.onGet(VULNERABILITY_FEEDBACK_ENDPOINT).reply(500, {});
vm = mountComponent(Component, { mrData: gl.mrWidgetData });
});
it('should render error indicator', done => {
setImmediate(() => {
expect(
findSecurityWidget()
.querySelector(COVERAGE_FUZZING_SELECTOR)
.textContent.trim(),
).toContain('Coverage fuzzing: Loading resulted in an error');
done();
});
});
});
});
describe('Secret Scanning', () => {
const SECRET_SCANNING_ENDPOINT = 'secret_scanning';
......
......@@ -99,8 +99,6 @@ key2: value2"
<!---->
<!---->
<vulnerability-detail-stub
label="Identifiers"
>
......
......@@ -33,7 +33,6 @@ describe('VulnerabilityDetails component', () => {
const findRequestHeaders = () => wrapper.find({ ref: 'requestHeaders' });
const findResponseHeaders = () => wrapper.find({ ref: 'responseHeaders' });
const findResponseStatusCode = () => wrapper.find({ ref: 'responseStatusCode' });
const findCrashState = () => wrapper.find({ ref: 'crashState' });
const findCrashAddress = () => wrapper.find({ ref: 'crashAddress' });
const findStacktraceSnippet = () => wrapper.find({ ref: 'stacktraceSnippet' });
......@@ -161,26 +160,18 @@ describe('VulnerabilityDetails component', () => {
describe('with coverage fuzzing information', () => {
beforeEach(() => {
const vulnerability = makeVulnerability({
stacktrace_snippet: 'test snippet',
location: {
crash_address: '0x602000001573',
crash_state: 'FuzzMe\nstart\nstart+0x0\n\n',
crash_type: 'Heap-buffer-overflow\nREAD 1',
stacktrace_snippet: 'test snippet',
},
});
componentFactory(vulnerability);
});
it('renders a code-block containing the crash_state', () => {
expect(findCrashState().is(CodeBlock)).toBe(true);
expect(findCrashState().text()).toBe('FuzzMe\nstart\nstart+0x0');
});
it('renders crash_address', () => {
expect(findCrashAddress().exists()).toBe(true);
});
it('renders stacktrace_snippet', () => {
expect(findStacktraceSnippet().exists()).toBe(true);
});
});
......
......@@ -22,6 +22,7 @@ import {
containerScanningDiffSuccessMock,
dependencyScanningDiffSuccessMock,
secretScanningDiffSuccessMock,
coverageFuzzingDiffSuccessMock,
mockFindings,
} from './mock_data';
......@@ -30,6 +31,7 @@ const DEPENDENCY_SCANNING_DIFF_ENDPOINT = 'dependency_scanning.json';
const DAST_DIFF_ENDPOINT = 'dast.json';
const SAST_DIFF_ENDPOINT = 'sast.json';
const SECRET_SCANNING_DIFF_ENDPOINT = 'secret_scanning.json';
const COVERAGE_FUZZING_DIFF_ENDPOINT = 'coverage_fuzzing.json';
describe('Grouped security reports app', () => {
let wrapper;
......@@ -48,6 +50,7 @@ describe('Grouped security reports app', () => {
canReadVulnerabilityFeedbackPath: true,
vulnerabilityFeedbackPath: 'vulnerability_feedback_path.json',
vulnerabilityFeedbackHelpPath: 'path',
coverageFuzzingHelpPath: 'path',
pipelineId: 123,
projectFullPath: 'path',
};
......@@ -96,6 +99,7 @@ describe('Grouped security reports app', () => {
containerScanning: true,
dependencyScanning: true,
secretScanning: true,
coverageFuzzing: true,
},
};
......@@ -106,6 +110,7 @@ describe('Grouped security reports app', () => {
gl.mrWidgetData.dast_comparison_path = DAST_DIFF_ENDPOINT;
gl.mrWidgetData.sast_comparison_path = SAST_DIFF_ENDPOINT;
gl.mrWidgetData.secret_scanning_comparison_path = SECRET_SCANNING_DIFF_ENDPOINT;
gl.mrWidgetData.coverage_fuzzing_comparison_path = COVERAGE_FUZZING_DIFF_ENDPOINT;
});
describe('with error', () => {
......@@ -115,6 +120,7 @@ describe('Grouped security reports app', () => {
mock.onGet(DAST_DIFF_ENDPOINT).reply(500);
mock.onGet(SAST_DIFF_ENDPOINT).reply(500);
mock.onGet(SECRET_SCANNING_DIFF_ENDPOINT).reply(500);
mock.onGet(COVERAGE_FUZZING_DIFF_ENDPOINT).reply(500);
createWrapper(allReportProps);
......@@ -124,6 +130,7 @@ describe('Grouped security reports app', () => {
waitForMutation(wrapper.vm.$store, types.RECEIVE_DAST_DIFF_ERROR),
waitForMutation(wrapper.vm.$store, types.RECEIVE_DEPENDENCY_SCANNING_DIFF_ERROR),
waitForMutation(wrapper.vm.$store, types.RECEIVE_SECRET_SCANNING_DIFF_ERROR),
waitForMutation(wrapper.vm.$store, types.RECEIVE_COVERAGE_FUZZING_DIFF_ERROR),
]);
});
......@@ -162,6 +169,7 @@ describe('Grouped security reports app', () => {
mock.onGet(DAST_DIFF_ENDPOINT).reply(200, {});
mock.onGet(SAST_DIFF_ENDPOINT).reply(200, {});
mock.onGet(SECRET_SCANNING_DIFF_ENDPOINT).reply(200, {});
mock.onGet(COVERAGE_FUZZING_DIFF_ENDPOINT).reply(200, {});
createWrapper(allReportProps);
});
......@@ -180,6 +188,7 @@ describe('Grouped security reports app', () => {
expect(wrapper.vm.$el.textContent).toContain('Dependency scanning is loading');
expect(wrapper.vm.$el.textContent).toContain('Container scanning is loading');
expect(wrapper.vm.$el.textContent).toContain('DAST is loading');
expect(wrapper.vm.$el.textContent).toContain('Coverage fuzzing is loading');
});
});
......@@ -191,6 +200,7 @@ describe('Grouped security reports app', () => {
mock.onGet(DAST_DIFF_ENDPOINT).reply(200, emptyResponse);
mock.onGet(SAST_DIFF_ENDPOINT).reply(200, emptyResponse);
mock.onGet(SECRET_SCANNING_DIFF_ENDPOINT).reply(200, emptyResponse);
mock.onGet(COVERAGE_FUZZING_DIFF_ENDPOINT).reply(200, emptyResponse);
createWrapper(allReportProps);
......@@ -200,6 +210,7 @@ describe('Grouped security reports app', () => {
waitForMutation(wrapper.vm.$store, types.RECEIVE_CONTAINER_SCANNING_DIFF_SUCCESS),
waitForMutation(wrapper.vm.$store, types.RECEIVE_DEPENDENCY_SCANNING_DIFF_SUCCESS),
waitForMutation(wrapper.vm.$store, types.RECEIVE_SECRET_SCANNING_DIFF_SUCCESS),
waitForMutation(wrapper.vm.$store, types.RECEIVE_COVERAGE_FUZZING_DIFF_SUCCESS),
]);
});
......@@ -239,6 +250,7 @@ describe('Grouped security reports app', () => {
mock.onGet(DAST_DIFF_ENDPOINT).reply(200, dastDiffSuccessMock);
mock.onGet(SAST_DIFF_ENDPOINT).reply(200, sastDiffSuccessMock);
mock.onGet(SECRET_SCANNING_DIFF_ENDPOINT).reply(200, secretScanningDiffSuccessMock);
mock.onGet(COVERAGE_FUZZING_DIFF_ENDPOINT).reply(200, coverageFuzzingDiffSuccessMock);
createWrapper(allReportProps);
......@@ -248,6 +260,7 @@ describe('Grouped security reports app', () => {
waitForMutation(wrapper.vm.$store, types.RECEIVE_CONTAINER_SCANNING_DIFF_SUCCESS),
waitForMutation(wrapper.vm.$store, types.RECEIVE_DEPENDENCY_SCANNING_DIFF_SUCCESS),
waitForMutation(wrapper.vm.$store, types.RECEIVE_SECRET_SCANNING_DIFF_SUCCESS),
waitForMutation(wrapper.vm.$store, types.RECEIVE_COVERAGE_FUZZING_DIFF_SUCCESS),
]);
});
......@@ -284,6 +297,11 @@ describe('Grouped security reports app', () => {
expect(wrapper.vm.$el.textContent).toContain(
'DAST detected 1 new critical severity vulnerability.',
);
// Renders container scanning result
expect(wrapper.vm.$el.textContent).toContain(
'Coverage fuzzing detected 1 new critical and 1 new high severity vulnerabilities.',
);
});
it('opens modal with more information', () => {
......@@ -307,6 +325,7 @@ describe('Grouped security reports app', () => {
${'container-scanning'} | ${containerScanningDiffSuccessMock.fixed} | ${containerScanningDiffSuccessMock.added}
${'dast'} | ${dastDiffSuccessMock.fixed} | ${dastDiffSuccessMock.added}
${'secret-scanning'} | ${secretScanningDiffSuccessMock.fixed} | ${secretScanningDiffSuccessMock.added}
${'coverage-fuzzing'} | ${coverageFuzzingDiffSuccessMock.fixed} | ${coverageFuzzingDiffSuccessMock.added}
`(
'renders a grouped-issues-list with the correct props for "$reportType" issues',
({ reportType, resolvedIssues, unresolvedIssues }) => {
......
......@@ -302,6 +302,31 @@ export const secretScanningFeedbacks = [
},
];
export const coverageFuzzingFeedbacks = [
{
id: 3,
project_id: 17,
author_id: 1,
issue_iid: null,
pipeline_id: 132,
category: 'coverage_fuzzing',
feedback_type: 'dismissal',
branch: 'try_new_coverage_fuzzing',
project_fingerprint: 'f55331d66fd4f3bfb4237d48e9c9fa8704bd33c6',
},
{
id: 4,
project_id: 17,
author_id: 1,
issue_iid: 123,
pipeline_id: 132,
category: 'coverage_fuzzing',
feedback_type: 'issue',
branch: 'try_new_coverage_fuzzing',
project_fingerprint: 'f55331d66fd4f3bfb4237d48e9c9fa8704bd33c6',
},
];
export const mockFindings = [
{
id: null,
......@@ -644,3 +669,11 @@ export const secretScanningDiffSuccessMock = {
base_report_out_of_date: false,
head_report_created_at: '2020-01-10T10:00:00.000Z',
};
export const coverageFuzzingDiffSuccessMock = {
added: [mockFindings[0], mockFindings[1]],
fixed: [mockFindings[2]],
base_report_created_at: '2020-01-01T10:00:00.000Z',
base_report_out_of_date: false,
head_report_created_at: '2020-01-10T10:00:00.000Z',
};
......@@ -9,6 +9,7 @@ import {
requestContainerScanningDiff,
requestDastDiff,
requestDependencyScanningDiff,
requestCoverageFuzzingDiff,
openModal,
setModalData,
requestDismissVulnerability,
......@@ -29,6 +30,7 @@ import {
updateContainerScanningIssue,
updateDastIssue,
updateSecretScanningIssue,
updateCoverageFuzzingIssue,
addDismissalComment,
receiveAddDismissalCommentError,
receiveAddDismissalCommentSuccess,
......@@ -55,6 +57,10 @@ import {
receiveSecretScanningDiffSuccess,
receiveSecretScanningDiffError,
fetchSecretScanningDiff,
setCoverageFuzzingDiffEndpoint,
receiveCoverageFuzzingDiffSuccess,
receiveCoverageFuzzingDiffError,
fetchCoverageFuzzingDiff,
} from 'ee/vue_shared/security_reports/store/actions';
import * as types from 'ee/vue_shared/security_reports/store/mutation_types';
import state from 'ee/vue_shared/security_reports/store/state';
......@@ -65,6 +71,7 @@ import {
containerScanningFeedbacks,
dependencyScanningFeedbacks,
secretScanningFeedbacks,
coverageFuzzingFeedbacks,
} from '../mock_data';
import toasted from '~/vue_shared/plugins/global_toast';
......@@ -272,6 +279,23 @@ describe('security reports actions', () => {
});
});
describe('requestCoverageFuzzingDiff', () => {
it('should commit request mutation', done => {
testAction(
requestCoverageFuzzingDiff,
null,
mockedState,
[
{
type: types.REQUEST_COVERAGE_FUZZING_DIFF,
},
],
[],
done,
);
});
});
describe('openModal', () => {
it('dispatches setModalData action', done => {
testAction(
......@@ -1102,6 +1126,26 @@ describe('security reports actions', () => {
});
});
describe('updateCoverageFuzzingIssue', () => {
it('commits update coverageFuzzing issue', done => {
const payload = { foo: 'bar' };
testAction(
updateCoverageFuzzingIssue,
payload,
mockedState,
[
{
type: types.UPDATE_COVERAGE_FUZZING_ISSUE,
payload,
},
],
[],
done,
);
});
});
describe('setContainerScanningDiffEndpoint', () => {
it('should pass down the endpoint to the mutation', done => {
const payload = '/container_scanning_endpoint.json';
......@@ -1851,4 +1895,161 @@ describe('security reports actions', () => {
});
});
});
describe('setCoverageFuzzingDiffEndpoint', () => {
it('should pass down the endpoint to the mutation', done => {
const payload = '/coverage_fuzzing_endpoint.json';
testAction(
setCoverageFuzzingDiffEndpoint,
payload,
mockedState,
[
{
type: types.SET_COVERAGE_FUZZING_DIFF_ENDPOINT,
payload,
},
],
[],
done,
);
});
});
describe('receiveCoverageFuzzingDiffSuccess', () => {
it('should pass down the response to the mutation', done => {
const payload = { data: 'Effort yields its own rewards.' };
testAction(
receiveCoverageFuzzingDiffSuccess,
payload,
mockedState,
[
{
type: types.RECEIVE_COVERAGE_FUZZING_DIFF_SUCCESS,
payload,
},
],
[],
done,
);
});
});
describe('receiveCoverageFuzzingDiffError', () => {
it('should commit coverage fuzzing diff error mutation', done => {
testAction(
receiveCoverageFuzzingDiffError,
undefined,
mockedState,
[
{
type: types.RECEIVE_COVERAGE_FUZZING_DIFF_ERROR,
},
],
[],
done,
);
});
});
describe('fetcCoverageFuzzingDiff', () => {
const diff = { foo: {} };
beforeEach(() => {
mockedState.vulnerabilityFeedbackPath = 'vulnerabilities_feedback';
mockedState.coverageFuzzing.paths.diffEndpoint = 'coverage_fuzzing_diff.json';
});
describe('on success', () => {
it('should dispatch `receiveCoverageFuzzingDiffSuccess`', done => {
mock.onGet('coverage_fuzzing_diff.json').reply(200, diff);
mock
.onGet('vulnerabilities_feedback', {
params: {
category: 'coverage_fuzzing',
},
})
.reply(200, coverageFuzzingFeedbacks);
testAction(
fetchCoverageFuzzingDiff,
null,
mockedState,
[],
[
{
type: 'requestCoverageFuzzingDiff',
},
{
type: 'receiveCoverageFuzzingDiffSuccess',
payload: {
diff,
enrichData: coverageFuzzingFeedbacks,
},
},
],
done,
);
});
});
describe('when vulnerabilities path errors', () => {
it('should dispatch `receiveCoverageFuzzingError`', done => {
mock.onGet('coverage_fuzzing_diff.json').reply(500);
mock
.onGet('vulnerabilities_feedback', {
params: {
category: 'coverage_fuzzing',
},
})
.reply(200, coverageFuzzingFeedbacks);
testAction(
fetchCoverageFuzzingDiff,
null,
mockedState,
[],
[
{
type: 'requestCoverageFuzzingDiff',
},
{
type: 'receiveCoverageFuzzingDiffError',
},
],
done,
);
});
});
describe('when feedback path errors', () => {
it('should dispatch `receiveCoverageFuzzingError`', done => {
mock.onGet('coverage_fuzzing_diff.json').reply(200, diff);
mock
.onGet('vulnerabilities_feedback', {
params: {
category: 'coverage_fuzzing',
},
})
.reply(500);
testAction(
fetchCoverageFuzzingDiff,
null,
mockedState,
[],
[
{
type: 'requestCoverageFuzzingDiff',
},
{
type: 'receiveCoverageFuzzingDiffError',
},
],
done,
);
});
});
});
});
......@@ -806,4 +806,91 @@ describe('security reports mutations', () => {
expect(stateCopy.secretScanning.hasError).toEqual(true);
});
});
describe('SET_COVERAGE_FUZZING_DIFF_ENDPOINT', () => {
const endpoint = 'coverage_fuzzing_diff_endpoint.json';
beforeEach(() => {
mutations[types.SET_COVERAGE_FUZZING_DIFF_ENDPOINT](stateCopy, endpoint);
});
it('should set the correct endpoint', () => {
expect(stateCopy.coverageFuzzing.paths.diffEndpoint).toEqual(endpoint);
});
});
describe('RECEIVE_COVERAGE_FUZZING_DIFF_SUCCESS', () => {
let reports = {};
const scans = [
{
scanned_resources_count: 123,
job_path: '/group/project/-/jobs/123546789',
},
{
scanned_resources_count: 321,
job_path: '/group/project/-/jobs/987654321',
},
];
beforeEach(() => {
reports = {
diff: {
added: [
{ name: 'added vuln 1', report_type: 'coverage_fuzzing' },
{ name: 'added vuln 2', report_type: 'coverage_fuzzing' },
],
fixed: [{ name: 'fixed vuln 1', report_type: 'coverage_fuzzing' }],
existing: [{ name: 'existing vuln 1', report_type: 'coverage_fuzzing' }],
base_report_out_of_date: true,
scans,
},
};
mutations[types.RECEIVE_COVERAGE_FUZZING_DIFF_SUCCESS](stateCopy, reports);
});
it('should set isLoading to false', () => {
expect(stateCopy.coverageFuzzing.isLoading).toBe(false);
});
it('should set scans', () => {
expect(stateCopy.coverageFuzzing.scans).toEqual(scans);
});
it('should set baseReportOutofDate to true', () => {
expect(stateCopy.coverageFuzzing.baseReportOutofDate).toBe(true);
});
it('should parse and set the added vulnerabilities', () => {
reports.diff.added.forEach((vuln, i) => {
expect(stateCopy.coverageFuzzing.newIssues[i]).toEqual(
expect.objectContaining({
name: vuln.name,
title: vuln.name,
category: vuln.report_type,
}),
);
});
});
it('should parse and set the fixed vulnerabilities', () => {
reports.diff.fixed.forEach((vuln, i) => {
expect(stateCopy.coverageFuzzing.resolvedIssues[i]).toEqual(
expect.objectContaining({
name: vuln.name,
title: vuln.name,
category: vuln.report_type,
}),
);
});
});
});
describe('RECEIVE_COVERAGE_FUZZING_DIFF_ERROR', () => {
it('should set coverage fuzzing loading flag to false and error flag to true', () => {
mutations[types.RECEIVE_COVERAGE_FUZZING_DIFF_ERROR](stateCopy);
expect(stateCopy.coverageFuzzing.isLoading).toEqual(false);
expect(stateCopy.coverageFuzzing.hasError).toEqual(true);
});
});
});
......@@ -420,6 +420,12 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
msgid "%{labelStart}Crash Address:%{labelEnd} %{crash_address}"
msgstr ""
msgid "%{labelStart}Crash State:%{labelEnd} %{stacktrace_snippet}"
msgstr ""
msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
......@@ -6824,9 +6830,6 @@ msgstr ""
msgid "Coverage Fuzzing"
msgstr ""
msgid "Crash State"
msgstr ""
msgid "Create"
msgstr ""
......@@ -27719,6 +27722,9 @@ msgstr ""
msgid "ciReport|%{linkStartTag}Learn more about Container Scanning %{linkEndTag}"
msgstr ""
msgid "ciReport|%{linkStartTag}Learn more about Coverage Fuzzing %{linkEndTag}"
msgstr ""
msgid "ciReport|%{linkStartTag}Learn more about DAST %{linkEndTag}"
msgstr ""
......@@ -27794,6 +27800,12 @@ msgstr ""
msgid "ciReport|Coverage Fuzzing"
msgstr ""
msgid "ciReport|Coverage Fuzzing Title"
msgstr ""
msgid "ciReport|Coverage fuzzing"
msgstr ""
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
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