Commit 858af8e3 authored by Phil Hughes's avatar Phil Hughes

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

Adds Vuex store for security reports

See merge request gitlab-org/gitlab-ee!5155
parents 31eb5747 8af1dee9
import axios from '~/lib/utils/axios_utils';
import * as types from './mutation_types';
export const setHeadBlobPath = ({ commit }, blobPath) => commit(types.SET_HEAD_BLOB_PATH, blobPath);
export const setBaseBlobPath = ({ commit }, blobPath) => commit(types.SET_BASE_BLOB_PATH, blobPath);
/**
* SAST
*/
export const setSastHeadPath = ({ commit }, path) => commit(types.SET_SAST_HEAD_PATH, path);
export const setSastBasePath = ({ commit }, path) => commit(types.SET_SAST_BASE_PATH, path);
export const requestSastReports = ({ commit }) => commit(types.REQUEST_SAST_REPORTS);
export const receiveSastReports = ({ commit }, response) =>
commit(types.RECEIVE_SAST_REPORTS, response);
export const receiveSastError = ({ commit }, error) =>
commit(types.RECEIVE_SAST_REPORTS_ERROR, error);
export const fetchSastReports = ({ state, dispatch }) => {
const base = state.sast.paths.base;
const head = state.sast.paths.head;
dispatch('requestSastReports');
Promise.all([
head ? axios.get(head) : Promise.resolve(),
base ? axios.get(base) : Promise.resolve(),
])
.then(values => {
dispatch('receiveSastReports', {
head: values[0] ? values[0].data : null,
base: values[1] ? values[1].data : null,
});
})
.catch(() => {
dispatch('receiveSastError');
});
};
/**
* SAST CONTAINER
*/
export const setSastContainerHeadPath = ({ commit }, path) =>
commit(types.SET_SAST_CONTAINER_HEAD_PATH, path);
export const setSastContainerBasePath = ({ commit }, path) =>
commit(types.SET_SAST_CONTAINER_BASE_PATH, path);
export const requestSastContainerReports = ({ commit }) =>
commit(types.REQUEST_SAST_CONTAINER_REPORTS);
export const receiveSastContainerReports = ({ commit }, response) =>
commit(types.RECEIVE_SAST_CONTAINER_REPORTS, response);
export const receiveSastContainerError = ({ commit }, error) =>
commit(types.RECEIVE_SAST_CONTAINER_ERROR, error);
export const fetchSastContainerReports = ({ state, dispatch }) => {
const base = state.sastContainer.paths.base;
const head = state.sastContainer.paths.head;
dispatch('requestSastContainerReports');
Promise.all([
head ? axios.get(head) : Promise.resolve(),
base ? axios.get(base) : Promise.resolve(),
])
.then(values => {
dispatch('receiveSastContainerReports', {
head: values[0] ? values[0].data : null,
base: values[1] ? values[1].data : null,
});
})
.catch(() => {
dispatch('receiveSastContainerError');
});
};
/**
* DAST
*/
export const setDastHeadPath = ({ commit }, path) => commit(types.SET_DAST_HEAD_PATH, path);
export const setDastBasePath = ({ commit }, path) => commit(types.SET_DAST_BASE_PATH, path);
export const requestDastReports = ({ commit }) => commit(types.REQUEST_DAST_REPORTS);
export const receiveDastReports = ({ commit }, response) =>
commit(types.RECEIVE_DAST_REPORTS, response);
export const receiveDastError = ({ commit }, error) => commit(types.RECEIVE_DAST_ERROR, error);
export const fetchDastReports = ({ state, dispatch }) => {
const base = state.dast.paths.base;
const head = state.dast.paths.head;
dispatch('requestDastReports');
Promise.all([
head ? axios.get(head) : Promise.resolve(),
base ? axios.get(base) : Promise.resolve(),
])
.then(values => {
dispatch('receiveDastReports', {
head: values[0] ? values[0].data : null,
base: values[1] ? values[1].data : null,
});
})
.catch(() => {
dispatch('receiveDastError');
});
};
/**
* DEPENDENCY SCANNING
*/
export const setDependencyScanningHeadPath = ({ commit }, path) =>
commit(types.SET_DEPENDENCY_SCANNING_HEAD_PATH, path);
export const setDependencyScanningBasePath = ({ commit }, path) =>
commit(types.SET_DEPENDENCY_SCANNING_BASE_PATH, path);
export const requestDependencyScanningReports = ({ commit }) =>
commit(types.REQUEST_DEPENDENCY_SCANNING_REPORTS);
export const receiveDependencyScanningReports = ({ commit }, response) =>
commit(types.RECEIVE_DEPENDENCY_SCANNING_REPORTS, response);
export const receiveDependencyScanningError = ({ commit }, error) =>
commit(types.RECEIVE_DEPENDENCY_SCANNING_ERROR, error);
export const fetchDependencyScanningReports = ({ state, dispatch }) => {
const base = state.dependencyScanning.paths.base;
const head = state.dependencyScanning.paths.head;
dispatch('requestDependencyScanningReports');
Promise.all([
head ? axios.get(head) : Promise.resolve(),
base ? axios.get(base) : Promise.resolve(),
])
.then(values => {
dispatch('receiveDependencyScanningReports', {
head: values[0] ? values[0].data : null,
base: values[1] ? values[1].data : null,
});
})
.catch(() => {
dispatch('receiveDependencyScanningError');
});
};
import { n__, s__ } from '~/locale';
import { textBuilder, statusIcon } from './utils';
export const groupedSastText = ({ sast }) =>
textBuilder(
'SAST',
sast.paths,
sast.newIssues.length,
sast.resolvedIssues.length,
sast.allIssues.length,
);
export const groupedSastContainerText = ({ sastContainer }) =>
textBuilder(
'Container scanning',
sastContainer.paths,
sastContainer.newIssues.length,
sastContainer.resolvedIssues.length,
);
export const groupedDastText = ({ dast }) =>
textBuilder('DAST', dast.paths, dast.newIssues.length, dast.resolvedIssues.length);
export const groupedDependencyText = ({ dependencyScanning }) =>
textBuilder(
'Dependency scanning',
dependencyScanning.paths,
dependencyScanning.newIssues.length,
dependencyScanning.resolvedIssues.length,
);
export const groupedSummaryText = (state, getters) => {
const { added, fixed } = state.summaryCounts;
// All reports returned error
if (getters.allReportsHaveError) {
return s__('ciReport|Security scanning failed loading any results');
}
// No base is present in any report
if (getters.noBaseInAllReports) {
if (added > 0) {
return n__(
'Security scanning was unable to compare existing and new vulnerabilities. It detected %d vulnerability',
'Security scanning was unable to compare existing and new vulnerabilities. It detected %d vulnerabilities',
added,
);
}
return s__(
'Security scanning was unable to compare existing and new vulnerabilities. It detected no vulnerabilities.',
);
}
const text = [s__('ciReport|Security scanning')];
if (getters.areReportsLoading) {
text.push('(in progress)');
}
if (added > 0 && fixed === 0) {
text.push(n__('detected %d new vulnerability', 'detected %d new vulnerabilities', added));
}
if (added > 0 && fixed > 0) {
text.push(
`${n__('detected %d new vulnerability', 'detected %d new vulnerabilities', added)} ${n__(
'and %d fixed vulnerability',
'and %d fixed vulnerabilities',
fixed,
)}`,
);
}
if (added === 0 && fixed > 0) {
text.push(n__('detected %d fixed vulnerability', 'detected %d fixed vulnerabilities', fixed));
}
if (added === 0 && fixed === 0) {
text.push(s__('detected no vulnerabilities'));
}
return text.join(' ');
};
export const sastStatusIcon = ({ sast }) => statusIcon(sast.hasError, sast.newIssues.length);
export const sastContainerStatusIcon = ({ sastContainer }) =>
statusIcon(sastContainer.hasError, sastContainer.newIssues.length);
export const dastStatusIcon = ({ dast }) => statusIcon(dast.hasError, dast.newIssues.length);
export const dependencyScanningStatusIcon = ({ dependencyScanning }) =>
statusIcon(dependencyScanning.hasError, dependencyScanning.newIssues.length);
export const areReportsLoading = state =>
state.sast.isLoading ||
state.dast.isLoading ||
state.sastContainer.isLoading ||
state.dependencyScanning.isLoading;
export const allReportsHaveError = state =>
state.sast.hasError &&
state.dast.hasError &&
state.sastContainer.hasError &&
state.dependencyScanning.hasError;
export const anyReportHasError = state =>
state.sast.hasError ||
state.dast.hasError ||
state.sastContainer.hasError ||
state.dependencyScanning.hasError;
export const noBaseInAllReports = state =>
!state.sast.paths.base &&
!state.dast.paths.base &&
!state.sastContainer.paths.base &&
!state.dependencyScanning.paths.base;
import Vue from 'vue';
import Vuex from 'vuex';
import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations';
import state from './state';
Vue.use(Vuex);
const store = new Vuex.Store({
actions,
getters,
mutations,
state: state(),
});
export default store;
export const SET_HEAD_BLOB_PATH = 'SET_HEAD_BLOB_PATH';
export const SET_BASE_BLOB_PATH = 'SET_BASE_BLOB_PATH';
// SAST
export const SET_SAST_HEAD_PATH = 'SET_SAST_HEAD_PATH';
export const SET_SAST_BASE_PATH = 'SET_SAST_BASE_PATH';
export const REQUEST_SAST_REPORTS = 'REQUEST_SAST_REPORTS';
export const RECEIVE_SAST_REPORTS = 'RECEIVE_SAST_REPORTS';
export const RECEIVE_SAST_REPORTS_ERROR = 'RECEIVE_SAST_REPORTS_ERROR';
// SAST CONTAINER
export const SET_SAST_CONTAINER_HEAD_PATH = 'SET_SAST_CONTAINER_HEAD_PATH';
export const SET_SAST_CONTAINER_BASE_PATH = 'SET_SAST_CONTAINER_BASE_PATH';
export const REQUEST_SAST_CONTAINER_REPORTS = 'REQUEST_SAST_CONTAINER_REPORTS';
export const RECEIVE_SAST_CONTAINER_REPORTS = 'RECEIVE_SAST_CONTAINER_REPORTS';
export const RECEIVE_SAST_CONTAINER_ERROR = 'RECEIVE_SAST_CONTAINER_ERROR';
// DAST
export const SET_DAST_HEAD_PATH = 'SET_DAST_HEAD_PATH';
export const SET_DAST_BASE_PATH = 'SET_DAST_BASE_PATH';
export const REQUEST_DAST_REPORTS = 'REQUEST_DAST_REPORTS';
export const RECEIVE_DAST_REPORTS = 'RECEIVE_DAST_REPORTS';
export const RECEIVE_DAST_ERROR = 'RECEIVE_DAST_ERROR';
// DEPENDENCY_SCANNING
export const SET_DEPENDENCY_SCANNING_HEAD_PATH = 'SET_DEPENDENCY_SCANNING_HEAD_PATH';
export const SET_DEPENDENCY_SCANNING_BASE_PATH = 'SET_DEPENDENCY_SCANNING_BASE_PATH';
export const REQUEST_DEPENDENCY_SCANNING_REPORTS = 'REQUEST_DEPENDENCY_SCANNING_REPORTS';
export const RECEIVE_DEPENDENCY_SCANNING_REPORTS = 'RECEIVE_DEPENDENCY_SCANNING_REPORTS';
export const RECEIVE_DEPENDENCY_SCANNING_ERROR = 'RECEIVE_DEPENDENCY_SCANNING_ERROR';
import * as types from './mutation_types';
import {
parseSastIssues,
filterByKey,
parseSastContainer,
parseDastIssues,
getUnapprovedVulnerabilities,
} from './utils';
export default {
[types.SET_HEAD_BLOB_PATH](state, path) {
Object.assign(state.blobPath, { head: path });
},
[types.SET_BASE_BLOB_PATH](state, path) {
Object.assign(state.blobPath, { base: path });
},
// SAST
[types.SET_SAST_HEAD_PATH](state, path) {
Object.assign(state.sast.paths, { head: path });
},
[types.SET_SAST_BASE_PATH](state, path) {
Object.assign(state.sast.paths, { base: path });
},
[types.REQUEST_SAST_REPORTS](state) {
Object.assign(state.sast, { isLoading: true });
},
/**
* Compares sast results and returns the formatted report
*
* Sast has 3 types of issues: newIssues, resolvedIssues and allIssues.
*
* When we have both base and head:
* - newIssues = head - base
* - resolvedIssues = base - head
* - allIssues = head - newIssues - resolvedIssues
*
* When we only have head
* - newIssues = head
* - resolvedIssues = 0
* - allIssues = 0
*/
[types.RECEIVE_SAST_REPORTS](state, reports) {
if (reports.base && reports.head) {
const filterKey = 'cve';
const parsedHead = parseSastIssues(reports.head, state.blobPath.head);
const parsedBase = parseSastIssues(reports.base, state.blobPath.base);
const newIssues = filterByKey(parsedHead, parsedBase, filterKey);
const resolvedIssues = filterByKey(parsedBase, parsedHead, filterKey);
const allIssues = filterByKey(parsedHead, newIssues.concat(resolvedIssues), filterKey);
Object.assign(state, {
sast: {
...state.sast,
newIssues,
resolvedIssues,
allIssues,
isLoading: false,
},
summaryCounts: {
added: state.summaryCounts.added + newIssues.length,
fixed: state.summaryCounts.fixed + resolvedIssues.length,
},
});
} else if (reports.head && !reports.base) {
const newIssues = parseSastIssues(reports.head, state.blobPath.head);
Object.assign(state.sast, {
newIssues,
isLoading: false,
});
}
},
[types.RECEIVE_SAST_REPORTS_ERROR](state) {
Object.assign(state.sast, {
isLoading: false,
hasError: true,
});
},
// SAST CONTAINER
[types.SET_SAST_CONTAINER_HEAD_PATH](state, path) {
Object.assign(state.sastContainer.paths, { head: path });
},
[types.SET_SAST_CONTAINER_BASE_PATH](state, path) {
Object.assign(state.sastContainer.paths, { base: path });
},
[types.REQUEST_SAST_CONTAINER_REPORTS](state) {
Object.assign(state.sastContainer, { isLoading: true });
},
/**
* For sast container we only render unapproved vulnerabilities.
*/
[types.RECEIVE_SAST_CONTAINER_REPORTS](state, reports) {
if (reports.base && reports.head) {
const headIssues = getUnapprovedVulnerabilities(
parseSastContainer(reports.head.vulnerabilities),
reports.head.unapproved,
);
const baseIssues = getUnapprovedVulnerabilities(
parseSastContainer(reports.base.vulnerabilities),
reports.base.unapproved,
);
const filterKey = 'vulnerability';
const newIssues = filterByKey(headIssues, baseIssues, filterKey);
const resolvedIssues = filterByKey(baseIssues, headIssues, filterKey);
Object.assign(state, {
sastContainer: {
...state.sastContainer,
isLoading: false,
newIssues,
resolvedIssues,
},
summaryCounts: {
added: state.summaryCounts.added + newIssues.length,
fixed: state.summaryCounts.fixed + resolvedIssues.length,
},
});
} else if (reports.head && !reports.base) {
Object.assign(state.sastContainer, {
isLoading: false,
newIssues: getUnapprovedVulnerabilities(
parseSastContainer(reports.head.vulnerabilities),
reports.head.unapproved,
),
});
}
},
[types.RECEIVE_SAST_CONTAINER_ERROR](state) {
Object.assign(state.sastContainer, {
isLoading: false,
hasError: true,
});
},
// DAST
[types.SET_DAST_HEAD_PATH](state, path) {
Object.assign(state.dast.paths, { head: path });
},
[types.SET_DAST_BASE_PATH](state, path) {
Object.assign(state.dast.paths, { base: path });
},
[types.REQUEST_DAST_REPORTS](state) {
Object.assign(state.dast, { isLoading: true });
},
[types.RECEIVE_DAST_REPORTS](state, reports) {
if (reports.head && reports.base) {
const headIssues = parseDastIssues(reports.head.site.alerts);
const baseIssues = parseDastIssues(reports.base.site.alerts);
const filterKey = 'pluginid';
const newIssues = filterByKey(headIssues, baseIssues, filterKey);
const resolvedIssues = filterByKey(baseIssues, headIssues, filterKey);
Object.assign(state, {
dast: {
...state.dast,
isLoading: false,
newIssues,
resolvedIssues,
},
summaryCounts: {
added: state.summaryCounts.added + newIssues.length,
fixed: state.summaryCounts.fixed + resolvedIssues.length,
},
});
} else if (reports.head && !reports.base) {
Object.assign(state.dast, {
isLoading: false,
newIssues: parseDastIssues(reports.head.site.alerts),
});
}
},
[types.RECEIVE_DAST_ERROR](state) {
Object.assign(state.dast, {
isLoading: false,
hasError: true,
});
},
// DEPENDECY SCANNING
[types.SET_DEPENDENCY_SCANNING_HEAD_PATH](state, path) {
Object.assign(state.dependencyScanning.paths, { head: path });
},
[types.SET_DEPENDENCY_SCANNING_BASE_PATH](state, path) {
Object.assign(state.dependencyScanning.paths, { base: path });
},
[types.REQUEST_DEPENDENCY_SCANNING_REPORTS](state) {
Object.assign(state.dependencyScanning, { isLoading: true });
},
/**
* Compares dependency scanning results and returns the formatted report
*
* Dependency report has 3 types of issues, newIssues, resolvedIssues and allIssues.
*
* When we have both base and head:
* - newIssues = head - base
* - resolvedIssues = base - head
* - allIssues = head - newIssues - resolvedIssues
*
* When we only have head
* - newIssues = head
* - resolvedIssues = 0
* - allIssues = 0
*/
[types.RECEIVE_DEPENDENCY_SCANNING_REPORTS](state, reports) {
if (reports.base && reports.head) {
const filterKey = 'cve';
const parsedHead = parseSastIssues(reports.head, state.blobPath.head);
const parsedBase = parseSastIssues(reports.base, state.blobPath.base);
const newIssues = filterByKey(parsedHead, parsedBase, filterKey);
const resolvedIssues = filterByKey(parsedBase, parsedHead, filterKey);
const allIssues = filterByKey(parsedHead, newIssues.concat(resolvedIssues), filterKey);
Object.assign(state, {
dependencyScanning: {
...state.dependencyScanning,
newIssues,
resolvedIssues,
allIssues,
isLoading: false,
},
summaryCounts: {
added: state.summaryCounts.added + newIssues.length,
fixed: state.summaryCounts.fixed + resolvedIssues.length,
},
});
} else {
Object.assign(state.dependencyScanning, {
newIssues: parseSastIssues(reports.head, state.blobPath.head),
isLoading: false,
});
}
},
[types.RECEIVE_DEPENDENCY_SCANNING_ERROR](state) {
Object.assign(state.dependencyScanning, {
isLoading: false,
hasError: true,
});
},
};
export default () => ({
summaryCounts: {
added: 0,
fixed: 0,
},
blobPath: {
head: null,
base: null,
},
sast: {
paths: {
head: null,
base: null,
},
isLoading: false,
hasError: false,
newIssues: [],
resolvedIssues: [],
allIssues: [],
},
sastContainer: {
paths: {
head: null,
base: null,
},
isLoading: false,
hasError: false,
newIssues: [],
resolvedIssues: [],
},
dast: {
paths: {
head: null,
base: null,
},
isLoading: false,
hasError: false,
newIssues: [],
resolvedIssues: [],
},
dependencyScanning: {
paths: {
head: null,
base: null,
},
isLoading: false,
hasError: false,
newIssues: [],
resolvedIssues: [],
allIssues: [],
},
});
import { stripHtml } from '~/lib/utils/text_utility';
import { n__, s__, sprintf } from '~/locale';
/**
* Maps SAST & Dependency scanning issues:
* { tool: String, message: String, url: String , cve: String ,
* file: String , solution: String, priority: String }
* to contain:
* { name: String, path: String, line: String, urlPath: String, priority: String }
* @param {Array} issues
* @param {String} path
*/
export const parseSastIssues = (issues = [], path = '') =>
issues.map(issue => ({
...issue,
name: issue.message,
path: issue.file,
urlPath: issue.line ? `${path}/${issue.file}#L${issue.line}` : `${path}/${issue.file}`,
}),
);
/**
* Parses Sast Container results into a common format to allow to use the same Vue component
* And adds an external link
*
* @param {Array} data
* @returns {Array}
*/
export const parseSastContainer = (data = []) =>
data.map(element => ({
...element,
name: element.vulnerability,
priority: element.severity,
path: element.namespace,
// external link to provide better description
nameLink: `https://cve.mitre.org/cgi-bin/cvename.cgi?name=${element.vulnerability}`,
}));
export const parseDastIssues = (issues = []) =>
issues.map(issue => ({
parsedDescription: stripHtml(issue.desc, ' '),
priority: issue.riskdesc,
...issue,
}));
/**
* Compares two arrays by the given key and returns the difference
*
* @param {Array} firstArray
* @param {Array} secondArray
* @param {String} key
* @returns {Array}
*/
export const filterByKey = (firstArray = [], secondArray = [], key = '') =>
firstArray.filter(item => !secondArray.find(el => el[key] === item[key]));
export const getUnapprovedVulnerabilities = (issues = [], unapproved = []) =>
issues.filter(item => unapproved.find(el => el === item.vulnerability));
export const textBuilder = (
type = '',
paths = {},
newIssues = 0,
resolvedIssues = 0,
allIssues = 0,
) => {
// With no issues
if (newIssues === 0 && resolvedIssues === 0 && allIssues === 0) {
return sprintf(s__('ciReport|%{type} detected no security vulnerabilities'), { type });
}
// with no new or fixed but with vulnerabilities
if (newIssues === 0 && resolvedIssues === 0 && allIssues) {
return sprintf(s__('ciReport|%{type} detected no new security vulnerabilities'), { type });
}
// with new issues and only head
if (newIssues > 0 && !paths.base) {
return sprintf(
n__(
'%{type} was unable to compare existing and new vulnerabilities. It detected %d vulnerability',
'%{type} was unable to compare existing and new vulnerabilities. It detected %d vulnerabilities',
newIssues,
),
{ type },
);
}
// with head + base
if (paths.base && paths.head) {
// with only new issues
if (newIssues > 0 && resolvedIssues === 0) {
return sprintf(
n__(
'%{type} detected %d new vulnerability',
'%{type} detected %d new vulnerabilities',
newIssues,
),
{ type },
);
}
// with new and fixed issues
if (newIssues > 0 && resolvedIssues > 0) {
return `${sprintf(
n__(
'%{type} detected %d new vulnerability',
'%{type} detected %d new vulnerabilities',
newIssues,
),
{ type },
)}
${n__('and %d fixed vulnerability', 'and %d fixed vulnerabilities', resolvedIssues)}`;
}
// with only fixed issues
if (newIssues === 0 && resolvedIssues > 0) {
return sprintf(
n__(
'%{type} detected %d fixed vulnerability',
'%{type} detected %d fixed vulnerabilities',
resolvedIssues,
),
{ type },
);
}
}
return '';
};
export const statusIcon = (failed = false, newIssues = 0, neutralIssues = 0) => {
if (failed || newIssues > 0 || neutralIssues > 0) {
return 'warning';
}
return 'success';
};
import state from 'ee/vue_shared/security_reports/store/state';
import mutations from 'ee/vue_shared/security_reports/store/mutations';
import * as types from 'ee/vue_shared/security_reports/store/mutation_types';
import {
sastIssues,
sastIssuesBase,
parsedSastIssuesHead,
parsedSastBaseStore,
dockerReport,
dockerBaseReport,
dockerNewIssues,
dockerOnlyHeadParsed,
dast,
dastBase,
parsedDastNewIssues,
parsedDast,
parsedSastIssuesStore,
} from '../mock_data';
describe('security reports mutations', () => {
let stateCopy;
beforeEach(() => {
stateCopy = state();
});
describe('SET_HEAD_BLOB_PATH', () => {
it('should set head blob path', () => {
mutations[types.SET_HEAD_BLOB_PATH](stateCopy, 'head_blob_path');
expect(stateCopy.blobPath.head).toEqual('head_blob_path');
});
});
describe('SET_BASE_BLOB_PATH', () => {
it('should set base blob path', () => {
mutations[types.SET_BASE_BLOB_PATH](stateCopy, 'base_blob_path');
expect(stateCopy.blobPath.base).toEqual('base_blob_path');
});
});
describe('SET_SAST_HEAD_PATH', () => {
it('should set sast head path', () => {
mutations[types.SET_SAST_HEAD_PATH](stateCopy, 'sast_head_path');
expect(stateCopy.sast.paths.head).toEqual('sast_head_path');
});
});
describe('SET_SAST_BASE_PATH', () => {
it('sets sast base path', () => {
mutations[types.SET_SAST_BASE_PATH](stateCopy, 'sast_base_path');
expect(stateCopy.sast.paths.base).toEqual('sast_base_path');
});
});
describe('REQUEST_SAST_REPORTS', () => {
it('should set sast loading flag to true', () => {
mutations[types.REQUEST_SAST_REPORTS](stateCopy);
expect(stateCopy.sast.isLoading).toEqual(true);
});
});
describe('RECEIVE_SAST_REPORTS', () => {
describe('with head and base', () => {
it('should set new, fixed and all issues', () => {
mutations[types.SET_BASE_BLOB_PATH](stateCopy, 'path');
mutations[types.SET_HEAD_BLOB_PATH](stateCopy, 'path');
mutations[types.RECEIVE_SAST_REPORTS](stateCopy, {
head: sastIssues,
base: sastIssuesBase,
});
expect(stateCopy.sast.isLoading).toEqual(false);
expect(stateCopy.sast.newIssues).toEqual(parsedSastIssuesHead);
expect(stateCopy.sast.resolvedIssues).toEqual(parsedSastBaseStore);
});
});
describe('with head', () => {
it('should set new issues', () => {
mutations[types.SET_HEAD_BLOB_PATH](stateCopy, 'path');
mutations[types.RECEIVE_SAST_REPORTS](stateCopy, {
head: sastIssues,
});
expect(stateCopy.sast.isLoading).toEqual(false);
expect(stateCopy.sast.newIssues).toEqual(parsedSastIssuesStore);
});
});
});
describe('RECEIVE_SAST_REPORTS_ERROR', () => {
it('should set loading flag to false and error flag to true for sast', () => {
mutations[types.RECEIVE_SAST_REPORTS_ERROR](stateCopy);
expect(stateCopy.sast.isLoading).toEqual(false);
expect(stateCopy.sast.hasError).toEqual(true);
});
});
describe('SET_SAST_CONTAINER_HEAD_PATH', () => {
it('should set sast container head path', () => {
mutations[types.SET_SAST_CONTAINER_HEAD_PATH](stateCopy, 'head_path');
expect(stateCopy.sastContainer.paths.head).toEqual('head_path');
});
});
describe('SET_SAST_CONTAINER_BASE_PATH', () => {
it('should set sast container base path', () => {
mutations[types.SET_SAST_CONTAINER_BASE_PATH](stateCopy, 'base_path');
expect(stateCopy.sastContainer.paths.base).toEqual('base_path');
});
});
describe('REQUEST_SAST_CONTAINER_REPORTS', () => {
it('should set sast container loading flag to true', () => {
mutations[types.REQUEST_SAST_CONTAINER_REPORTS](stateCopy);
expect(stateCopy.sastContainer.isLoading).toEqual(true);
});
});
describe('RECEIVE_SAST_CONTAINER_REPORTS', () => {
describe('with head and base', () => {
it('should set new and resolved issues', () => {
mutations[types.RECEIVE_SAST_CONTAINER_REPORTS](stateCopy, {
head: dockerReport,
base: dockerBaseReport,
});
expect(stateCopy.sastContainer.isLoading).toEqual(false);
expect(stateCopy.sastContainer.newIssues).toEqual(dockerNewIssues);
expect(stateCopy.sastContainer.resolvedIssues).toEqual([]);
});
});
describe('with head', () => {
it('should set new issues', () => {
mutations[types.RECEIVE_SAST_CONTAINER_REPORTS](stateCopy, {
head: dockerReport,
});
expect(stateCopy.sastContainer.isLoading).toEqual(false);
expect(stateCopy.sastContainer.newIssues).toEqual(dockerOnlyHeadParsed);
});
});
});
describe('RECEIVE_SAST_CONTAINER_ERROR', () => {
it('should set sast container loading flag to false and error flag to true', () => {
mutations[types.RECEIVE_SAST_CONTAINER_ERROR](stateCopy);
expect(stateCopy.sastContainer.isLoading).toEqual(false);
expect(stateCopy.sastContainer.hasError).toEqual(true);
});
});
describe('SET_DAST_HEAD_PATH', () => {
it('should set dast head path', () => {
mutations[types.SET_DAST_HEAD_PATH](stateCopy, 'head_path');
expect(stateCopy.dast.paths.head).toEqual('head_path');
});
});
describe('SET_DAST_BASE_PATH', () => {
it('should set dast base path', () => {
mutations[types.SET_DAST_BASE_PATH](stateCopy, 'base_path');
expect(stateCopy.dast.paths.base).toEqual('base_path');
});
});
describe('REQUEST_DAST_REPORTS', () => {
it('should set dast loading flag to true', () => {
mutations[types.REQUEST_DAST_REPORTS](stateCopy);
expect(stateCopy.dast.isLoading).toEqual(true);
});
});
describe('RECEIVE_DAST_REPORTS', () => {
describe('with head and base', () => {
it('sets new and resolved issues with the given data', () => {
mutations[types.RECEIVE_DAST_REPORTS](stateCopy, {
head: dast,
base: dastBase,
});
expect(stateCopy.dast.isLoading).toEqual(false);
expect(stateCopy.dast.newIssues).toEqual(parsedDastNewIssues);
expect(stateCopy.dast.resolvedIssues).toEqual([]);
});
});
describe('with head', () => {
it('sets new issues with the given data', () => {
mutations[types.RECEIVE_DAST_REPORTS](stateCopy, {
head: dast,
});
expect(stateCopy.dast.isLoading).toEqual(false);
expect(stateCopy.dast.newIssues).toEqual(parsedDast);
});
});
});
describe('RECEIVE_DAST_ERROR', () => {
it('should set dast loading flag to false and error flag to true', () => {
mutations[types.RECEIVE_DAST_ERROR](stateCopy);
expect(stateCopy.dast.isLoading).toEqual(false);
expect(stateCopy.dast.hasError).toEqual(true);
});
});
describe('SET_DEPENDENCY_SCANNING_HEAD_PATH', () => {
it('should set dependency scanning head path', () => {
mutations[types.SET_DEPENDENCY_SCANNING_HEAD_PATH](stateCopy, 'head_path');
expect(stateCopy.dependencyScanning.paths.head).toEqual('head_path');
});
});
describe('SET_DEPENDENCY_SCANNING_BASE_PATH', () => {
it('should set dependency scanning base path', () => {
mutations[types.SET_DEPENDENCY_SCANNING_BASE_PATH](stateCopy, 'base_path');
expect(stateCopy.dependencyScanning.paths.base).toEqual('base_path');
});
});
describe('REQUEST_DEPENDENCY_SCANNING_REPORTS', () => {
it('should set dependency scanning loading flag to true', () => {
mutations[types.REQUEST_DEPENDENCY_SCANNING_REPORTS](stateCopy);
expect(stateCopy.dependencyScanning.isLoading).toEqual(true);
});
});
describe('RECEIVE_DEPENDENCY_SCANNING_REPORTS', () => {
describe('with head and base', () => {
it('should set new, fixed and all issues', () => {
mutations[types.SET_BASE_BLOB_PATH](stateCopy, 'path');
mutations[types.SET_HEAD_BLOB_PATH](stateCopy, 'path');
mutations[types.RECEIVE_DEPENDENCY_SCANNING_REPORTS](stateCopy, {
head: sastIssues,
base: sastIssuesBase,
});
expect(stateCopy.dependencyScanning.isLoading).toEqual(false);
expect(stateCopy.dependencyScanning.newIssues).toEqual(parsedSastIssuesHead);
expect(stateCopy.dependencyScanning.resolvedIssues).toEqual(parsedSastBaseStore);
});
});
describe('with head', () => {
it('should set new issues', () => {
mutations[types.SET_HEAD_BLOB_PATH](stateCopy, 'path');
mutations[types.RECEIVE_DEPENDENCY_SCANNING_REPORTS](stateCopy, {
head: sastIssues,
});
expect(stateCopy.dependencyScanning.isLoading).toEqual(false);
expect(stateCopy.dependencyScanning.newIssues).toEqual(parsedSastIssuesStore);
});
});
});
describe('RECEIVE_DEPENDENCY_SCANNING_ERROR', () => {
it('should set dependency scanning loading flag to false and error flag to true', () => {
mutations[types.RECEIVE_DEPENDENCY_SCANNING_ERROR](stateCopy);
expect(stateCopy.dependencyScanning.isLoading).toEqual(false);
expect(stateCopy.dependencyScanning.hasError).toEqual(true);
});
});
});
import {
parseSastIssues,
parseSastContainer,
parseDastIssues,
filterByKey,
getUnapprovedVulnerabilities,
textBuilder,
statusIcon,
} from 'ee/vue_shared/security_reports/store/utils';
import { sastIssues, dockerReport, dast, parsedDast } from '../mock_data';
describe('security reports utils', () => {
describe('parseSastIssues', () => {
it('should parse the received issues', () => {
const security = parseSastIssues(sastIssues, 'path')[0];
expect(security.name).toEqual(sastIssues[0].message);
expect(security.path).toEqual(sastIssues[0].file);
});
});
describe('parseSastContainer', () => {
it('parses sast container issues', () => {
const parsed = parseSastContainer(dockerReport.vulnerabilities)[0];
expect(parsed.name).toEqual(dockerReport.vulnerabilities[0].vulnerability);
expect(parsed.priority).toEqual(dockerReport.vulnerabilities[0].severity);
expect(parsed.path).toEqual(dockerReport.vulnerabilities[0].namespace);
expect(parsed.nameLink).toEqual(
`https://cve.mitre.org/cgi-bin/cvename.cgi?name=${
dockerReport.vulnerabilities[0].vulnerability
}`,
);
});
});
describe('parseDastIssues', () => {
it('parsed dast report', () => {
expect(parseDastIssues(dast.site.alerts)).toEqual(parsedDast);
});
});
describe('filterByKey', () => {
it('filters the array with the provided key', () => {
const array1 = [{ id: '1234' }, { id: 'abg543' }, { id: '214swfA' }];
const array2 = [{ id: '1234' }, { id: 'abg543' }, { id: '453OJKs' }];
expect(filterByKey(array1, array2, 'id')).toEqual([{ id: '214swfA' }]);
});
});
describe('getUnapprovedVulnerabilities', () => {
it('return unapproved vulnerabilities', () => {
const unapproved = getUnapprovedVulnerabilities(
dockerReport.vulnerabilities,
dockerReport.unapproved,
);
expect(unapproved.length).toEqual(dockerReport.unapproved.length);
expect(unapproved[0].vulnerability).toEqual(dockerReport.unapproved[0]);
expect(unapproved[1].vulnerability).toEqual(dockerReport.unapproved[1]);
});
});
describe('textBuilder', () => {
describe('with no issues', () => {
it('should return no vulnerabiltities text', () => {
expect(textBuilder()).toEqual(' detected no security vulnerabilities');
});
});
describe('with only `all` issues', () => {
it('should return no new vulnerabiltities text', () => {
expect(textBuilder('', {}, 0, 0, 1)).toEqual(' detected no new security vulnerabilities');
});
});
describe('with new issues and without base', () => {
it('should return unable to compare text', () => {
expect(textBuilder('', { head: 'foo' }, 1, 0, 0)).toEqual(
' was unable to compare existing and new vulnerabilities. It detected 1 vulnerability',
);
});
});
describe('with base and head', () => {
describe('with only new issues', () => {
it('should return new issues text', () => {
expect(textBuilder('', { head: 'foo', base: 'foo' }, 1, 0, 0)).toEqual(
' detected 1 new vulnerability',
);
});
});
describe('with new and resolved issues', () => {
it('should return new and fixed issues text', () => {
expect(
textBuilder('', { head: 'foo', base: 'foo' }, 1, 1, 0).replace(/\n+\s+/m, ' '),
).toEqual(' detected 1 new vulnerability and 1 fixed vulnerability');
});
});
describe('with only resolved issues', () => {
it('should return fixed issues text', () => {
expect(textBuilder('', { head: 'foo', base: 'foo' }, 0, 1, 0)).toEqual(
' detected 1 fixed vulnerability',
);
});
});
});
});
describe('statusIcon', () => {
describe('with failed report', () => {
it('returns warning', () => {
expect(statusIcon(true)).toEqual('warning');
});
});
describe('with new issues', () => {
it('returns warning', () => {
expect(statusIcon(false, 1)).toEqual('warning');
});
});
describe('with neutral issues', () => {
it('returns warning', () => {
expect(statusIcon(false, 0, 1)).toEqual('warning');
});
});
describe('without new or neutal issues', () => {
it('returns success', () => {
expect(statusIcon()).toEqual('success');
});
});
});
});
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