Commit ffc54040 authored by Fatih Acet's avatar Fatih Acet

Merge branch '11975-move-SAST-to-the-frontend' into 'master'

Splits out SAST reports to a new module

See merge request gitlab-org/gitlab-ee!14299
parents 0b1bcb60 4f7a24a7
...@@ -155,17 +155,16 @@ export default { ...@@ -155,17 +155,16 @@ export default {
'canCreateFeedbackPermission', 'canCreateFeedbackPermission',
]), ]),
...mapGetters([ ...mapGetters([
'groupedSastText',
'groupedSummaryText', 'groupedSummaryText',
'summaryStatus', 'summaryStatus',
'groupedSastContainerText', 'groupedSastContainerText',
'groupedDastText', 'groupedDastText',
'groupedDependencyText', 'groupedDependencyText',
'sastStatusIcon',
'sastContainerStatusIcon', 'sastContainerStatusIcon',
'dastStatusIcon', 'dastStatusIcon',
'dependencyScanningStatusIcon', 'dependencyScanningStatusIcon',
]), ]),
...mapGetters('sast', ['groupedSastText', 'sastStatusIcon']),
securityTab() { securityTab() {
return `${this.pipelinePath}/security`; return `${this.pipelinePath}/security`;
}, },
...@@ -230,15 +229,12 @@ export default { ...@@ -230,15 +229,12 @@ export default {
'setHeadBlobPath', 'setHeadBlobPath',
'setBaseBlobPath', 'setBaseBlobPath',
'setSourceBranch', 'setSourceBranch',
'setSastHeadPath',
'setSastBasePath',
'setSastContainerHeadPath', 'setSastContainerHeadPath',
'setSastContainerBasePath', 'setSastContainerBasePath',
'setDastHeadPath', 'setDastHeadPath',
'setDastBasePath', 'setDastBasePath',
'setDependencyScanningHeadPath', 'setDependencyScanningHeadPath',
'setDependencyScanningBasePath', 'setDependencyScanningBasePath',
'fetchSastReports',
'fetchSastContainerReports', 'fetchSastContainerReports',
'fetchDastReports', 'fetchDastReports',
'fetchDependencyScanningReports', 'fetchDependencyScanningReports',
...@@ -259,6 +255,11 @@ export default { ...@@ -259,6 +255,11 @@ export default {
'downloadPatch', 'downloadPatch',
'addDismissalComment', 'addDismissalComment',
]), ]),
...mapActions('sast', {
setSastHeadPath: 'setHeadPath',
setSastBasePath: 'setBasePath',
fetchSastReports: 'fetchReports',
}),
}, },
}; };
</script> </script>
......
...@@ -214,11 +214,9 @@ export default { ...@@ -214,11 +214,9 @@ export default {
...mapActions([ ...mapActions([
'setHeadBlobPath', 'setHeadBlobPath',
'setSourceBranch', 'setSourceBranch',
'setSastHeadPath',
'setDependencyScanningHeadPath', 'setDependencyScanningHeadPath',
'setSastContainerHeadPath', 'setSastContainerHeadPath',
'setDastHeadPath', 'setDastHeadPath',
'fetchSastReports',
'fetchDependencyScanningReports', 'fetchDependencyScanningReports',
'fetchSastContainerReports', 'fetchSastContainerReports',
'fetchDastReports', 'fetchDastReports',
...@@ -239,6 +237,10 @@ export default { ...@@ -239,6 +237,10 @@ export default {
'downloadPatch', 'downloadPatch',
'addDismissalComment', 'addDismissalComment',
]), ]),
...mapActions('sast', {
setSastHeadPath: 'setHeadPath',
fetchSastReports: 'fetchReports',
}),
summaryTextBuilder(reportType, issuesCount = 0) { summaryTextBuilder(reportType, issuesCount = 0) {
if (issuesCount === 0) { if (issuesCount === 0) {
return sprintf(s__('ciReport|%{reportType} detected no vulnerabilities'), { return sprintf(s__('ciReport|%{reportType} detected no vulnerabilities'), {
......
...@@ -45,49 +45,6 @@ export const setCanCreateIssuePermission = ({ commit }, permission) => ...@@ -45,49 +45,6 @@ export const setCanCreateIssuePermission = ({ commit }, permission) =>
export const setCanCreateFeedbackPermission = ({ commit }, permission) => export const setCanCreateFeedbackPermission = ({ commit }, permission) =>
commit(types.SET_CAN_CREATE_FEEDBACK_PERMISSION, permission); commit(types.SET_CAN_CREATE_FEEDBACK_PERMISSION, permission);
/**
* 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, head } = state.sast.paths;
dispatch('requestSastReports');
return Promise.all([
head ? axios.get(head) : Promise.resolve(),
base ? axios.get(base) : Promise.resolve(),
axios.get(state.vulnerabilityFeedbackPath, {
params: {
category: 'sast',
},
}),
])
.then(values => {
dispatch('receiveSastReports', {
head: values && values[0] ? values[0].data : null,
base: values && values[1] ? values[1].data : null,
enrichData: values && values[2] ? values[2].data : [],
});
})
.catch(() => {
dispatch('receiveSastError');
});
};
export const updateSastIssue = ({ commit }, issue) => commit(types.UPDATE_SAST_ISSUE, issue);
/** /**
* SAST CONTAINER * SAST CONTAINER
*/ */
...@@ -233,8 +190,8 @@ export const openModal = ({ dispatch }, payload) => { ...@@ -233,8 +190,8 @@ export const openModal = ({ dispatch }, payload) => {
export const setModalData = ({ commit }, payload) => commit(types.SET_ISSUE_MODAL_DATA, payload); export const setModalData = ({ commit }, payload) => commit(types.SET_ISSUE_MODAL_DATA, payload);
export const requestDismissVulnerability = ({ commit }) => export const requestDismissVulnerability = ({ commit }) =>
commit(types.REQUEST_DISMISS_VULNERABILITY); commit(types.REQUEST_DISMISS_VULNERABILITY);
export const receiveDismissVulnerability = ({ commit }) => export const receiveDismissVulnerability = ({ commit }, payload) =>
commit(types.RECEIVE_DISMISS_VULNERABILITY_SUCCESS); commit(types.RECEIVE_DISMISS_VULNERABILITY_SUCCESS, payload);
export const receiveDismissVulnerabilityError = ({ commit }, error) => export const receiveDismissVulnerabilityError = ({ commit }, error) =>
commit(types.RECEIVE_DISMISS_VULNERABILITY_ERROR, error); commit(types.RECEIVE_DISMISS_VULNERABILITY_ERROR, error);
...@@ -253,30 +210,14 @@ export const dismissVulnerability = ({ state, dispatch }, comment) => { ...@@ -253,30 +210,14 @@ export const dismissVulnerability = ({ state, dispatch }, comment) => {
}, },
}) })
.then(({ data }) => { .then(({ data }) => {
dispatch('closeDismissalCommentBox');
dispatch('receiveDismissVulnerability');
// Update the issue with the created dismissal feedback applied
const updatedIssue = { const updatedIssue = {
...state.modal.vulnerability, ...state.modal.vulnerability,
isDismissed: true, isDismissed: true,
dismissalFeedback: data, dismissalFeedback: data,
}; };
switch (updatedIssue.category) {
case 'sast': dispatch('closeDismissalCommentBox');
dispatch('updateSastIssue', updatedIssue); dispatch('receiveDismissVulnerability', updatedIssue);
break;
case 'dependency_scanning':
dispatch('updateDependencyScanningIssue', updatedIssue);
break;
case 'container_scanning':
dispatch('updateContainerScanningIssue', updatedIssue);
break;
case 'dast':
dispatch('updateDastIssue', updatedIssue);
break;
default:
}
hideModal(); hideModal();
}) })
...@@ -334,29 +275,13 @@ export const revertDismissVulnerability = ({ state, dispatch }) => { ...@@ -334,29 +275,13 @@ export const revertDismissVulnerability = ({ state, dispatch }) => {
state.modal.vulnerability.dismissalFeedback.destroy_vulnerability_feedback_dismissal_path, state.modal.vulnerability.dismissalFeedback.destroy_vulnerability_feedback_dismissal_path,
) )
.then(() => { .then(() => {
dispatch('receiveDismissVulnerability');
// Update the issue with the reverted dismissal feedback applied
const updatedIssue = { const updatedIssue = {
...state.modal.vulnerability, ...state.modal.vulnerability,
isDismissed: false, isDismissed: false,
dismissalFeedback: null, dismissalFeedback: null,
}; };
switch (updatedIssue.category) {
case 'sast': dispatch('receiveDismissVulnerability', updatedIssue);
dispatch('updateSastIssue', updatedIssue);
break;
case 'dependency_scanning':
dispatch('updateDependencyScanningIssue', updatedIssue);
break;
case 'container_scanning':
dispatch('updateContainerScanningIssue', updatedIssue);
break;
case 'dast':
dispatch('updateDastIssue', updatedIssue);
break;
default:
}
hideModal(); hideModal();
}) })
......
import { s__, sprintf } from '~/locale'; import { s__, sprintf } from '~/locale';
import { countIssues, groupedTextBuilder, statusIcon } from './utils'; import { countIssues, groupedTextBuilder, statusIcon, groupedReportText } from './utils';
import { LOADING, ERROR, SUCCESS } from './constants'; import { LOADING, ERROR, SUCCESS } from './constants';
import messages from './messages'; import messages from './messages';
const groupedReportText = (report, reportType, errorMessage, loadingMessage) => {
const { paths } = report;
if (report.hasError) {
return errorMessage;
}
if (report.isLoading) {
return loadingMessage;
}
return groupedTextBuilder({
...countIssues(report),
reportType,
paths,
});
};
export const groupedSastText = ({ sast }) =>
groupedReportText(sast, messages.SAST, messages.SAST_HAS_ERROR, messages.SAST_IS_LOADING);
export const groupedSastContainerText = ({ sastContainer }) => export const groupedSastContainerText = ({ sastContainer }) =>
groupedReportText( groupedReportText(
sastContainer, sastContainer,
...@@ -102,9 +81,6 @@ export const summaryStatus = (state, getters) => { ...@@ -102,9 +81,6 @@ export const summaryStatus = (state, getters) => {
return SUCCESS; return SUCCESS;
}; };
export const sastStatusIcon = ({ sast }) =>
statusIcon(sast.isLoading, sast.hasError, sast.newIssues.length);
export const sastContainerStatusIcon = ({ sastContainer }) => export const sastContainerStatusIcon = ({ sastContainer }) =>
statusIcon(sastContainer.isLoading, sastContainer.hasError, sastContainer.newIssues.length); statusIcon(sastContainer.isLoading, sastContainer.hasError, sastContainer.newIssues.length);
......
import Vue from 'vue'; import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import configureMediator from './mediator';
import * as actions from './actions'; import * as actions from './actions';
import * as getters from './getters'; import * as getters from './getters';
import mutations from './mutations'; import mutations from './mutations';
import state from './state'; import state from './state';
import sast from './modules/sast';
Vue.use(Vuex); Vue.use(Vuex);
export default () => export default () =>
new Vuex.Store({ new Vuex.Store({
modules: {
sast,
},
actions, actions,
getters, getters,
mutations, mutations,
state: state(), state: state(),
plugins: [configureMediator],
}); });
import * as types from './mutation_types';
const updateIssueActionsMap = {
sast: 'sast/updateVulnerability',
dependency_scanning: 'updateDependencyScanningIssue',
container_scanning: 'updateContainerScanningIssue',
dast: 'updateDastIssue',
};
export default function configureMediator(store) {
store.subscribe(({ type, payload }) => {
switch (type) {
case types.RECEIVE_DISMISS_VULNERABILITY_SUCCESS:
if (updateIssueActionsMap[payload.category]) {
store.dispatch(updateIssueActionsMap[payload.category], payload);
}
break;
default:
}
});
}
import axios from '~/lib/utils/axios_utils';
import * as types from './mutation_types';
export const setHeadPath = ({ commit }, path) => commit(types.SET_HEAD_PATH, path);
export const setBasePath = ({ commit }, path) => commit(types.SET_BASE_PATH, path);
export const requestReports = ({ commit }) => commit(types.REQUEST_REPORTS);
export const receiveReports = ({ commit }, response) => commit(types.RECEIVE_REPORTS, response);
export const receiveError = ({ commit }, error) => commit(types.RECEIVE_REPORTS_ERROR, error);
export const fetchReports = ({ state, rootState, dispatch }) => {
const { base, head } = state.paths;
const { blobPath, vulnerabilityFeedbackPath } = rootState;
dispatch('requestReports');
return Promise.all([
head ? axios.get(head) : Promise.resolve(),
base ? axios.get(base) : Promise.resolve(),
axios.get(vulnerabilityFeedbackPath, {
params: {
category: 'sast',
},
}),
])
.then(values => {
dispatch('receiveReports', {
reports: {
head: values && values[0] ? values[0].data : null,
base: values && values[1] ? values[1].data : null,
enrichData: values && values[2] ? values[2].data : [],
},
blobPath,
});
})
.catch(() => {
dispatch('receiveError');
});
};
export const updateVulnerability = ({ commit }, vulnerability) =>
commit(types.UPDATE_VULNERABILITY, vulnerability);
export default () => {};
import messages from '../../messages';
export const { SAST, SAST_HAS_ERROR, SAST_IS_LOADING } = messages;
import { statusIcon, groupedReportText } from '../../utils';
import { SAST, SAST_HAS_ERROR, SAST_IS_LOADING } from './constants';
export const groupedSastText = state =>
groupedReportText(state, SAST, SAST_HAS_ERROR, SAST_IS_LOADING);
export const sastStatusIcon = ({ isLoading, hasError, newIssues }) =>
statusIcon(isLoading, hasError, newIssues.length);
export default () => {};
import state from './state';
import mutations from './mutations';
import * as getters from './getters';
import * as actions from './actions';
export default {
namespaced: true,
state,
mutations,
getters,
actions,
};
export const SET_HEAD_PATH = 'SET_HEAD_PATH';
export const SET_BASE_PATH = 'SET_BASE_PATH';
export const REQUEST_REPORTS = 'REQUEST_REPORTS';
export const RECEIVE_REPORTS = 'RECEIVE_REPORTS';
export const RECEIVE_REPORTS_ERROR = 'RECEIVE_REPORTS_ERROR';
export const UPDATE_VULNERABILITY = 'UPDATE_VULNERABILITY';
import Vue from 'vue';
import * as types from './mutation_types';
import { parseSastIssues, findIssueIndex } from '../../utils';
import filterByKey from '../../utils/filter_by_key';
export default {
[types.SET_HEAD_PATH](state, path) {
Vue.set(state.paths, 'head', path);
},
[types.SET_BASE_PATH](state, path) {
Vue.set(state.paths, 'base', path);
},
[types.REQUEST_REPORTS](state) {
state.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_REPORTS](state, payload) {
const { reports, blobPath } = payload;
if (reports.base && reports.head) {
const filterKey = 'cve';
const parsedHead = parseSastIssues(reports.head, reports.enrichData, blobPath.head);
const parsedBase = parseSastIssues(reports.base, reports.enrichData, blobPath.base);
const newIssues = filterByKey(parsedHead, parsedBase, filterKey);
const resolvedIssues = filterByKey(parsedBase, parsedHead, filterKey);
const allIssues = filterByKey(parsedHead, newIssues.concat(resolvedIssues), filterKey);
state.newIssues = newIssues;
state.resolvedIssues = resolvedIssues;
state.allIssues = allIssues;
state.isLoading = false;
} else if (reports.head && !reports.base) {
const newIssues = parseSastIssues(reports.head, reports.enrichData, blobPath.head);
state.newIssues = newIssues;
state.isLoading = false;
}
},
[types.RECEIVE_REPORTS_ERROR](state) {
state.isLoading = false;
state.hasError = true;
},
[types.UPDATE_VULNERABILITY](state, issue) {
const newIssuesIndex = findIssueIndex(state.newIssues, issue);
if (newIssuesIndex !== -1) {
state.newIssues.splice(newIssuesIndex, 1, issue);
return;
}
const resolvedIssuesIndex = findIssueIndex(state.resolvedIssues, issue);
if (resolvedIssuesIndex !== -1) {
state.resolvedIssues.splice(resolvedIssuesIndex, 1, issue);
return;
}
const allIssuesIndex = findIssueIndex(state.allIssues, issue);
if (allIssuesIndex !== -1) {
state.allIssues.splice(allIssuesIndex, 1, issue);
}
},
};
export default () => ({
paths: {
head: null,
base: null,
},
isLoading: false,
hasError: false,
newIssues: [],
resolvedIssues: [],
allIssues: [],
});
...@@ -13,13 +13,6 @@ export const SET_PIPELINE_ID = 'SET_PIPELINE_ID'; ...@@ -13,13 +13,6 @@ export const SET_PIPELINE_ID = 'SET_PIPELINE_ID';
export const SET_CAN_CREATE_ISSUE_PERMISSION = 'SET_CAN_CREATE_ISSUE_PERMISSION'; export const SET_CAN_CREATE_ISSUE_PERMISSION = 'SET_CAN_CREATE_ISSUE_PERMISSION';
export const SET_CAN_CREATE_FEEDBACK_PERMISSION = 'SET_CAN_CREATE_FEEDBACK_PERMISSION'; export const SET_CAN_CREATE_FEEDBACK_PERMISSION = 'SET_CAN_CREATE_FEEDBACK_PERMISSION';
// 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 // SAST CONTAINER
export const SET_SAST_CONTAINER_HEAD_PATH = 'SET_SAST_CONTAINER_HEAD_PATH'; 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 SET_SAST_CONTAINER_BASE_PATH = 'SET_SAST_CONTAINER_BASE_PATH';
...@@ -58,7 +51,6 @@ export const REQUEST_CREATE_MERGE_REQUEST = 'REQUEST_CREATE_MERGE_REQUEST'; ...@@ -58,7 +51,6 @@ export const REQUEST_CREATE_MERGE_REQUEST = 'REQUEST_CREATE_MERGE_REQUEST';
export const RECEIVE_CREATE_MERGE_REQUEST_SUCCESS = 'RECEIVE_CREATE_MERGE_REQUEST_SUCCESS'; export const RECEIVE_CREATE_MERGE_REQUEST_SUCCESS = 'RECEIVE_CREATE_MERGE_REQUEST_SUCCESS';
export const RECEIVE_CREATE_MERGE_REQUEST_ERROR = 'RECEIVE_CREATE_MERGE_REQUEST_ERROR'; export const RECEIVE_CREATE_MERGE_REQUEST_ERROR = 'RECEIVE_CREATE_MERGE_REQUEST_ERROR';
export const UPDATE_SAST_ISSUE = 'UPDATE_SAST_ISSUE';
export const UPDATE_DEPENDENCY_SCANNING_ISSUE = 'UPDATE_DEPENDENCY_SCANNING_ISSUE'; export const UPDATE_DEPENDENCY_SCANNING_ISSUE = 'UPDATE_DEPENDENCY_SCANNING_ISSUE';
export const UPDATE_CONTAINER_SCANNING_ISSUE = 'UPDATE_CONTAINER_SCANNING_ISSUE'; export const UPDATE_CONTAINER_SCANNING_ISSUE = 'UPDATE_CONTAINER_SCANNING_ISSUE';
export const UPDATE_DAST_ISSUE = 'UPDATE_DAST_ISSUE'; export const UPDATE_DAST_ISSUE = 'UPDATE_DAST_ISSUE';
......
import Vue from 'vue'; import Vue from 'vue';
import * as types from './mutation_types'; import * as types from './mutation_types';
import { import {
parseSastIssues,
parseDependencyScanningIssues, parseDependencyScanningIssues,
getDastSite, getDastSite,
parseDastIssues, parseDastIssues,
...@@ -57,61 +56,6 @@ export default { ...@@ -57,61 +56,6 @@ export default {
state.canCreateFeedbackPermission = permission; state.canCreateFeedbackPermission = permission;
}, },
// SAST
[types.SET_SAST_HEAD_PATH](state, path) {
Vue.set(state.sast.paths, 'head', path);
},
[types.SET_SAST_BASE_PATH](state, path) {
Vue.set(state.sast.paths, 'base', path);
},
[types.REQUEST_SAST_REPORTS](state) {
Vue.set(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, reports.enrichData, state.blobPath.head);
const parsedBase = parseSastIssues(reports.base, reports.enrichData, state.blobPath.base);
const newIssues = filterByKey(parsedHead, parsedBase, filterKey);
const resolvedIssues = filterByKey(parsedBase, parsedHead, filterKey);
const allIssues = filterByKey(parsedHead, newIssues.concat(resolvedIssues), filterKey);
Vue.set(state.sast, 'newIssues', newIssues);
Vue.set(state.sast, 'resolvedIssues', resolvedIssues);
Vue.set(state.sast, 'allIssues', allIssues);
Vue.set(state.sast, 'isLoading', false);
} else if (reports.head && !reports.base) {
const newIssues = parseSastIssues(reports.head, reports.enrichData, state.blobPath.head);
Vue.set(state.sast, 'newIssues', newIssues);
Vue.set(state.sast, 'isLoading', false);
}
},
[types.RECEIVE_SAST_REPORTS_ERROR](state) {
Vue.set(state.sast, 'isLoading', false);
Vue.set(state.sast, 'hasError', true);
},
// SAST CONTAINER // SAST CONTAINER
[types.SET_SAST_CONTAINER_HEAD_PATH](state, path) { [types.SET_SAST_CONTAINER_HEAD_PATH](state, path) {
Vue.set(state.sastContainer.paths, 'head', path); Vue.set(state.sastContainer.paths, 'head', path);
...@@ -342,27 +286,6 @@ export default { ...@@ -342,27 +286,6 @@ export default {
Vue.set(state.modal, 'error', error); Vue.set(state.modal, 'error', error);
}, },
[types.UPDATE_SAST_ISSUE](state, issue) {
// Find issue in the correct list and update it
const newIssuesIndex = findIssueIndex(state.sast.newIssues, issue);
if (newIssuesIndex !== -1) {
state.sast.newIssues.splice(newIssuesIndex, 1, issue);
return;
}
const resolvedIssuesIndex = findIssueIndex(state.sast.resolvedIssues, issue);
if (resolvedIssuesIndex !== -1) {
state.sast.resolvedIssues.splice(resolvedIssuesIndex, 1, issue);
return;
}
const allIssuesIndex = findIssueIndex(state.sast.allIssues, issue);
if (allIssuesIndex !== -1) {
state.sast.allIssues.splice(allIssuesIndex, 1, issue);
}
},
[types.UPDATE_DEPENDENCY_SCANNING_ISSUE](state, issue) { [types.UPDATE_DEPENDENCY_SCANNING_ISSUE](state, issue) {
// Find issue in the correct list and update it // Find issue in the correct list and update it
......
...@@ -16,19 +16,6 @@ export default () => ({ ...@@ -16,19 +16,6 @@ export default () => ({
canCreateIssuePermission: false, canCreateIssuePermission: false,
canCreateFeedbackPermission: false, canCreateFeedbackPermission: false,
sast: {
paths: {
head: null,
base: null,
},
isLoading: false,
hasError: false,
newIssues: [],
resolvedIssues: [],
allIssues: [],
},
sastContainer: { sastContainer: {
paths: { paths: {
head: null, head: null,
......
...@@ -387,3 +387,30 @@ export const countIssues = ({ newIssues = [], resolvedIssues = [], allIssues = [ ...@@ -387,3 +387,30 @@ export const countIssues = ({ newIssues = [], resolvedIssues = [], allIssues = [
fixed: resolvedIssues.length, fixed: resolvedIssues.length,
}; };
}; };
/**
* Generates a report message based on some of the report parameters and supplied messages.
*
* @param {Object} report The report to generate the text for
* @param {String} reportType The report type. e.g. SAST
* @param {String} errorMessage The message to show if there's an error in the report
* @param {String} loadingMessage The message to show if the report is still loading
* @returns {String}
*/
export const groupedReportText = (report, reportType, errorMessage, loadingMessage) => {
const { paths } = report;
if (report.hasError) {
return errorMessage;
}
if (report.isLoading) {
return loadingMessage;
}
return groupedTextBuilder({
...countIssues(report),
reportType,
paths,
});
};
import state from 'ee/vue_shared/security_reports/store/state'; import createState from 'ee/vue_shared/security_reports/store/state';
import createSastState from 'ee/vue_shared/security_reports/store/modules/sast/state';
import { import {
groupedSastText,
groupedSastContainerText, groupedSastContainerText,
groupedDastText, groupedDastText,
groupedDependencyText, groupedDependencyText,
...@@ -8,7 +8,6 @@ import { ...@@ -8,7 +8,6 @@ import {
allReportsHaveError, allReportsHaveError,
noBaseInAllReports, noBaseInAllReports,
areReportsLoading, areReportsLoading,
sastStatusIcon,
sastContainerStatusIcon, sastContainerStatusIcon,
dastStatusIcon, dastStatusIcon,
dependencyScanningStatusIcon, dependencyScanningStatusIcon,
...@@ -24,116 +23,20 @@ describe('Security reports getters', () => { ...@@ -24,116 +23,20 @@ describe('Security reports getters', () => {
return data.replace(/\r?\n|\r/g, '').replace(/\s\s+/g, ' '); return data.replace(/\r?\n|\r/g, '').replace(/\s\s+/g, ' ');
} }
describe('groupedSastText', () => { let state;
describe('with no issues', () => {
it('returns no issues text', () => {
const newState = state();
newState.sast.paths.head = HEAD_PATH;
newState.sast.paths.base = BASE_PATH;
expect(groupedSastText(newState)).toEqual('SAST detected no vulnerabilities');
});
});
describe('with only `all` issues', () => {
it('returns no new issues text', () => {
const newState = state();
newState.sast.paths.head = HEAD_PATH;
newState.sast.paths.base = BASE_PATH;
newState.sast.allIssues = [{}];
expect(groupedSastText(newState)).toEqual('SAST detected no new vulnerabilities');
});
});
describe('with new issues and without base', () => {
it('returns unable to compare text', () => {
const newState = state();
newState.sast.paths.head = HEAD_PATH;
newState.sast.newIssues = [{}];
expect(groupedSastText(newState)).toEqual(
'SAST detected 1 vulnerability for the source branch only',
);
});
});
describe('with base and head', () => {
describe('with only new issues', () => {
it('returns new issues text', () => {
const newState = state();
newState.sast.paths.head = HEAD_PATH;
newState.sast.paths.base = BASE_PATH;
newState.sast.newIssues = [{}];
expect(groupedSastText(newState)).toEqual('SAST detected 1 new vulnerability');
});
});
describe('with only dismissed issues', () => {
it('returns dismissed issues text', () => {
const newState = state();
newState.sast.paths.head = HEAD_PATH;
newState.sast.paths.base = BASE_PATH;
newState.sast.newIssues = [{ isDismissed: true }];
expect(groupedSastText(newState)).toEqual('SAST detected 1 dismissed vulnerability');
});
});
describe('with new and resolved issues', () => { beforeEach(() => {
it('returns new and fixed issues text', () => { state = createState();
const newState = state(); state.sast = createSastState();
newState.sast.paths.head = HEAD_PATH;
newState.sast.paths.base = BASE_PATH;
newState.sast.newIssues = [{}];
newState.sast.resolvedIssues = [{}];
expect(removeBreakLine(groupedSastText(newState))).toEqual(
'SAST detected 1 new, and 1 fixed vulnerabilities',
);
});
});
describe('with only resolved issues', () => {
it('returns fixed issues text', () => {
const newState = state();
newState.sast.paths.head = HEAD_PATH;
newState.sast.paths.base = BASE_PATH;
newState.sast.resolvedIssues = [{}];
expect(groupedSastText(newState)).toEqual('SAST detected 1 fixed vulnerability');
});
});
describe('with error', () => {
it('returns error text', () => {
const newState = state();
newState.sast.hasError = true;
expect(groupedSastText(newState)).toEqual('SAST: Loading resulted in an error');
});
});
describe('while loading', () => {
it('returns loading text', () => {
const newState = state();
newState.sast.isLoading = true;
expect(groupedSastText(newState)).toEqual('SAST is loading');
});
});
});
}); });
describe('groupedSastContainerText', () => { describe('groupedSastContainerText', () => {
describe('with no issues', () => { describe('with no issues', () => {
it('returns no issues text', () => { it('returns no issues text', () => {
const newState = state(); state.sastContainer.paths.head = HEAD_PATH;
newState.sastContainer.paths.head = HEAD_PATH; state.sastContainer.paths.base = BASE_PATH;
newState.sastContainer.paths.base = BASE_PATH;
expect(groupedSastContainerText(newState)).toEqual( expect(groupedSastContainerText(state)).toEqual(
'Container scanning detected no vulnerabilities', 'Container scanning detected no vulnerabilities',
); );
}); });
...@@ -141,11 +44,10 @@ describe('Security reports getters', () => { ...@@ -141,11 +44,10 @@ describe('Security reports getters', () => {
describe('with new issues and without base', () => { describe('with new issues and without base', () => {
it('returns unable to compare text', () => { it('returns unable to compare text', () => {
const newState = state(); state.sastContainer.paths.head = HEAD_PATH;
newState.sastContainer.paths.head = HEAD_PATH; state.sastContainer.newIssues = [{}];
newState.sastContainer.newIssues = [{}];
expect(groupedSastContainerText(newState)).toEqual( expect(groupedSastContainerText(state)).toEqual(
'Container scanning detected 1 vulnerability for the source branch only', 'Container scanning detected 1 vulnerability for the source branch only',
); );
}); });
...@@ -154,12 +56,11 @@ describe('Security reports getters', () => { ...@@ -154,12 +56,11 @@ describe('Security reports getters', () => {
describe('with base and head', () => { describe('with base and head', () => {
describe('with only new issues', () => { describe('with only new issues', () => {
it('returns new issues text', () => { it('returns new issues text', () => {
const newState = state(); state.sastContainer.paths.head = HEAD_PATH;
newState.sastContainer.paths.head = HEAD_PATH; state.sastContainer.paths.base = BASE_PATH;
newState.sastContainer.paths.base = BASE_PATH; state.sastContainer.newIssues = [{}];
newState.sastContainer.newIssues = [{}];
expect(groupedSastContainerText(newState)).toEqual( expect(groupedSastContainerText(state)).toEqual(
'Container scanning detected 1 new vulnerability', 'Container scanning detected 1 new vulnerability',
); );
}); });
...@@ -167,12 +68,11 @@ describe('Security reports getters', () => { ...@@ -167,12 +68,11 @@ describe('Security reports getters', () => {
describe('with only dismissed issues', () => { describe('with only dismissed issues', () => {
it('returns dismissed issues text', () => { it('returns dismissed issues text', () => {
const newState = state(); state.sastContainer.paths.head = HEAD_PATH;
newState.sastContainer.paths.head = HEAD_PATH; state.sastContainer.paths.base = BASE_PATH;
newState.sastContainer.paths.base = BASE_PATH; state.sastContainer.newIssues = [{ isDismissed: true }];
newState.sastContainer.newIssues = [{ isDismissed: true }];
expect(groupedSastContainerText(newState)).toEqual( expect(groupedSastContainerText(state)).toEqual(
'Container scanning detected 1 dismissed vulnerability', 'Container scanning detected 1 dismissed vulnerability',
); );
}); });
...@@ -180,13 +80,12 @@ describe('Security reports getters', () => { ...@@ -180,13 +80,12 @@ describe('Security reports getters', () => {
describe('with new and resolved issues', () => { describe('with new and resolved issues', () => {
it('returns new and fixed issues text', () => { it('returns new and fixed issues text', () => {
const newState = state(); state.sastContainer.paths.head = HEAD_PATH;
newState.sastContainer.paths.head = HEAD_PATH; state.sastContainer.paths.base = BASE_PATH;
newState.sastContainer.paths.base = BASE_PATH; state.sastContainer.newIssues = [{}];
newState.sastContainer.newIssues = [{}]; state.sastContainer.resolvedIssues = [{}];
newState.sastContainer.resolvedIssues = [{}];
expect(removeBreakLine(groupedSastContainerText(newState))).toEqual( expect(removeBreakLine(groupedSastContainerText(state))).toEqual(
'Container scanning detected 1 new, and 1 fixed vulnerabilities', 'Container scanning detected 1 new, and 1 fixed vulnerabilities',
); );
}); });
...@@ -194,12 +93,11 @@ describe('Security reports getters', () => { ...@@ -194,12 +93,11 @@ describe('Security reports getters', () => {
describe('with only resolved issues', () => { describe('with only resolved issues', () => {
it('returns fixed issues text', () => { it('returns fixed issues text', () => {
const newState = state(); state.sastContainer.paths.head = HEAD_PATH;
newState.sastContainer.paths.head = HEAD_PATH; state.sastContainer.paths.base = BASE_PATH;
newState.sastContainer.paths.base = BASE_PATH; state.sastContainer.resolvedIssues = [{}];
newState.sastContainer.resolvedIssues = [{}];
expect(groupedSastContainerText(newState)).toEqual( expect(groupedSastContainerText(state)).toEqual(
'Container scanning detected 1 fixed vulnerability', 'Container scanning detected 1 fixed vulnerability',
); );
}); });
...@@ -210,21 +108,19 @@ describe('Security reports getters', () => { ...@@ -210,21 +108,19 @@ describe('Security reports getters', () => {
describe('groupedDastText', () => { describe('groupedDastText', () => {
describe('with no issues', () => { describe('with no issues', () => {
it('returns no issues text', () => { it('returns no issues text', () => {
const newState = state(); state.dast.paths.head = HEAD_PATH;
newState.dast.paths.head = HEAD_PATH; state.dast.paths.base = BASE_PATH;
newState.dast.paths.base = BASE_PATH;
expect(groupedDastText(newState)).toEqual('DAST detected no vulnerabilities'); expect(groupedDastText(state)).toEqual('DAST detected no vulnerabilities');
}); });
}); });
describe('with new issues and without base', () => { describe('with new issues and without base', () => {
it('returns unable to compare text', () => { it('returns unable to compare text', () => {
const newState = state(); state.dast.paths.head = HEAD_PATH;
newState.dast.paths.head = HEAD_PATH; state.dast.newIssues = [{}];
newState.dast.newIssues = [{}];
expect(groupedDastText(newState)).toEqual( expect(groupedDastText(state)).toEqual(
'DAST detected 1 vulnerability for the source branch only', 'DAST detected 1 vulnerability for the source branch only',
); );
}); });
...@@ -233,35 +129,32 @@ describe('Security reports getters', () => { ...@@ -233,35 +129,32 @@ describe('Security reports getters', () => {
describe('with base and head', () => { describe('with base and head', () => {
describe('with only new issues', () => { describe('with only new issues', () => {
it('returns new issues text', () => { it('returns new issues text', () => {
const newState = state(); state.dast.paths.head = HEAD_PATH;
newState.dast.paths.head = HEAD_PATH; state.dast.paths.base = BASE_PATH;
newState.dast.paths.base = BASE_PATH; state.dast.newIssues = [{}];
newState.dast.newIssues = [{}];
expect(groupedDastText(newState)).toEqual('DAST detected 1 new vulnerability'); expect(groupedDastText(state)).toEqual('DAST detected 1 new vulnerability');
}); });
}); });
describe('with only dismissed issues', () => { describe('with only dismissed issues', () => {
it('returns dismissed issues text', () => { it('returns dismissed issues text', () => {
const newState = state(); state.dast.paths.head = HEAD_PATH;
newState.dast.paths.head = HEAD_PATH; state.dast.paths.base = BASE_PATH;
newState.dast.paths.base = BASE_PATH; state.dast.newIssues = [{ isDismissed: true }];
newState.dast.newIssues = [{ isDismissed: true }];
expect(groupedDastText(newState)).toEqual('DAST detected 1 dismissed vulnerability'); expect(groupedDastText(state)).toEqual('DAST detected 1 dismissed vulnerability');
}); });
}); });
describe('with new and resolved issues', () => { describe('with new and resolved issues', () => {
it('returns new and fixed issues text', () => { it('returns new and fixed issues text', () => {
const newState = state(); state.dast.paths.head = HEAD_PATH;
newState.dast.paths.head = HEAD_PATH; state.dast.paths.base = BASE_PATH;
newState.dast.paths.base = BASE_PATH; state.dast.newIssues = [{}];
newState.dast.newIssues = [{}]; state.dast.resolvedIssues = [{}];
newState.dast.resolvedIssues = [{}];
expect(removeBreakLine(groupedDastText(newState))).toEqual( expect(removeBreakLine(groupedDastText(state))).toEqual(
'DAST detected 1 new, and 1 fixed vulnerabilities', 'DAST detected 1 new, and 1 fixed vulnerabilities',
); );
}); });
...@@ -269,12 +162,11 @@ describe('Security reports getters', () => { ...@@ -269,12 +162,11 @@ describe('Security reports getters', () => {
describe('with only resolved issues', () => { describe('with only resolved issues', () => {
it('returns fixed issues text', () => { it('returns fixed issues text', () => {
const newState = state(); state.dast.paths.head = HEAD_PATH;
newState.dast.paths.head = HEAD_PATH; state.dast.paths.base = BASE_PATH;
newState.dast.paths.base = BASE_PATH; state.dast.resolvedIssues = [{}];
newState.dast.resolvedIssues = [{}];
expect(groupedDastText(newState)).toEqual('DAST detected 1 fixed vulnerability'); expect(groupedDastText(state)).toEqual('DAST detected 1 fixed vulnerability');
}); });
}); });
}); });
...@@ -283,11 +175,10 @@ describe('Security reports getters', () => { ...@@ -283,11 +175,10 @@ describe('Security reports getters', () => {
describe('groupedDependencyText', () => { describe('groupedDependencyText', () => {
describe('with no issues', () => { describe('with no issues', () => {
it('returns no issues text', () => { it('returns no issues text', () => {
const newState = state(); state.dependencyScanning.paths.head = HEAD_PATH;
newState.dependencyScanning.paths.head = HEAD_PATH; state.dependencyScanning.paths.base = BASE_PATH;
newState.dependencyScanning.paths.base = BASE_PATH;
expect(groupedDependencyText(newState)).toEqual( expect(groupedDependencyText(state)).toEqual(
'Dependency scanning detected no vulnerabilities', 'Dependency scanning detected no vulnerabilities',
); );
}); });
...@@ -295,11 +186,10 @@ describe('Security reports getters', () => { ...@@ -295,11 +186,10 @@ describe('Security reports getters', () => {
describe('with new issues and without base', () => { describe('with new issues and without base', () => {
it('returns unable to compare text', () => { it('returns unable to compare text', () => {
const newState = state(); state.dependencyScanning.paths.head = HEAD_PATH;
newState.dependencyScanning.paths.head = HEAD_PATH; state.dependencyScanning.newIssues = [{}];
newState.dependencyScanning.newIssues = [{}];
expect(groupedDependencyText(newState)).toEqual( expect(groupedDependencyText(state)).toEqual(
'Dependency scanning detected 1 vulnerability for the source branch only', 'Dependency scanning detected 1 vulnerability for the source branch only',
); );
}); });
...@@ -308,12 +198,11 @@ describe('Security reports getters', () => { ...@@ -308,12 +198,11 @@ describe('Security reports getters', () => {
describe('with base and head', () => { describe('with base and head', () => {
describe('with only new issues', () => { describe('with only new issues', () => {
it('returns new issues text', () => { it('returns new issues text', () => {
const newState = state(); state.dependencyScanning.paths.head = HEAD_PATH;
newState.dependencyScanning.paths.head = HEAD_PATH; state.dependencyScanning.paths.base = BASE_PATH;
newState.dependencyScanning.paths.base = BASE_PATH; state.dependencyScanning.newIssues = [{}];
newState.dependencyScanning.newIssues = [{}];
expect(groupedDependencyText(newState)).toEqual( expect(groupedDependencyText(state)).toEqual(
'Dependency scanning detected 1 new vulnerability', 'Dependency scanning detected 1 new vulnerability',
); );
}); });
...@@ -321,12 +210,11 @@ describe('Security reports getters', () => { ...@@ -321,12 +210,11 @@ describe('Security reports getters', () => {
describe('with only dismissed issues', () => { describe('with only dismissed issues', () => {
it('returns dismissed issues text', () => { it('returns dismissed issues text', () => {
const newState = state(); state.dependencyScanning.paths.head = HEAD_PATH;
newState.dependencyScanning.paths.head = HEAD_PATH; state.dependencyScanning.paths.base = BASE_PATH;
newState.dependencyScanning.paths.base = BASE_PATH; state.dependencyScanning.newIssues = [{ isDismissed: true }];
newState.dependencyScanning.newIssues = [{ isDismissed: true }];
expect(groupedDependencyText(newState)).toEqual( expect(groupedDependencyText(state)).toEqual(
'Dependency scanning detected 1 dismissed vulnerability', 'Dependency scanning detected 1 dismissed vulnerability',
); );
}); });
...@@ -334,13 +222,12 @@ describe('Security reports getters', () => { ...@@ -334,13 +222,12 @@ describe('Security reports getters', () => {
describe('with new and resolved issues', () => { describe('with new and resolved issues', () => {
it('returns new and fixed issues text', () => { it('returns new and fixed issues text', () => {
const newState = state(); state.dependencyScanning.paths.head = HEAD_PATH;
newState.dependencyScanning.paths.head = HEAD_PATH; state.dependencyScanning.paths.base = BASE_PATH;
newState.dependencyScanning.paths.base = BASE_PATH; state.dependencyScanning.newIssues = [{}];
newState.dependencyScanning.newIssues = [{}]; state.dependencyScanning.resolvedIssues = [{}];
newState.dependencyScanning.resolvedIssues = [{}];
expect(removeBreakLine(groupedDependencyText(newState))).toEqual( expect(removeBreakLine(groupedDependencyText(state))).toEqual(
'Dependency scanning detected 1 new, and 1 fixed vulnerabilities', 'Dependency scanning detected 1 new, and 1 fixed vulnerabilities',
); );
}); });
...@@ -348,13 +235,12 @@ describe('Security reports getters', () => { ...@@ -348,13 +235,12 @@ describe('Security reports getters', () => {
describe('with only resolved issues', () => { describe('with only resolved issues', () => {
it('returns fixed issues text', () => { it('returns fixed issues text', () => {
const newState = state(); state.dependencyScanning.paths.head = HEAD_PATH;
newState.dependencyScanning.paths.head = HEAD_PATH; state.dependencyScanning.paths.base = BASE_PATH;
newState.dependencyScanning.paths.base = BASE_PATH;
newState.dependencyScanning.resolvedIssues = [{}]; state.dependencyScanning.resolvedIssues = [{}];
expect(groupedDependencyText(newState)).toEqual( expect(groupedDependencyText(state)).toEqual(
'Dependency scanning detected 1 fixed vulnerability', 'Dependency scanning detected 1 fixed vulnerability',
); );
}); });
...@@ -364,9 +250,7 @@ describe('Security reports getters', () => { ...@@ -364,9 +250,7 @@ describe('Security reports getters', () => {
describe('summaryCounts', () => { describe('summaryCounts', () => {
it('returns 0 count for empty state', () => { it('returns 0 count for empty state', () => {
const newState = state(); expect(summaryCounts(state)).toEqual({
expect(summaryCounts(newState)).toEqual({
added: 0, added: 0,
dismissed: 0, dismissed: 0,
existing: 0, existing: 0,
...@@ -376,30 +260,25 @@ describe('Security reports getters', () => { ...@@ -376,30 +260,25 @@ describe('Security reports getters', () => {
describe('combines all reports', () => { describe('combines all reports', () => {
it('of the same type', () => { it('of the same type', () => {
const newState = state(); state.sastContainer.resolvedIssues = [{}];
state.dast.resolvedIssues = [{}];
state.dependencyScanning.resolvedIssues = [{}];
newState.sast.resolvedIssues = [{}]; expect(summaryCounts(state)).toEqual({
newState.sastContainer.resolvedIssues = [{}];
newState.dast.resolvedIssues = [{}];
newState.dependencyScanning.resolvedIssues = [{}];
expect(summaryCounts(newState)).toEqual({
added: 0, added: 0,
dismissed: 0, dismissed: 0,
existing: 0, existing: 0,
fixed: 4, fixed: 3,
}); });
}); });
it('of the different types', () => { it('of the different types', () => {
const newState = state(); state.sastContainer.resolvedIssues = [{}];
state.dast.allIssues = [{}];
newState.sast.allIssues = [{}]; state.dast.newIssues = [{ isDismissed: true }];
newState.sastContainer.resolvedIssues = [{}]; state.dependencyScanning.newIssues = [{ isDismissed: false }];
newState.dast.newIssues = [{ isDismissed: true }];
newState.dependencyScanning.newIssues = [{ isDismissed: false }];
expect(summaryCounts(newState)).toEqual({ expect(summaryCounts(state)).toEqual({
added: 1, added: 1,
dismissed: 1, dismissed: 1,
existing: 1, existing: 1,
...@@ -412,7 +291,7 @@ describe('Security reports getters', () => { ...@@ -412,7 +291,7 @@ describe('Security reports getters', () => {
describe('groupedSummaryText', () => { describe('groupedSummaryText', () => {
it('returns failed text', () => { it('returns failed text', () => {
expect( expect(
groupedSummaryText(state(), { groupedSummaryText(state, {
allReportsHaveError: true, allReportsHaveError: true,
noBaseInAllReports: false, noBaseInAllReports: false,
areReportsLoading: false, areReportsLoading: false,
...@@ -423,7 +302,7 @@ describe('Security reports getters', () => { ...@@ -423,7 +302,7 @@ describe('Security reports getters', () => {
it('returns no compare text', () => { it('returns no compare text', () => {
expect( expect(
groupedSummaryText(state(), { groupedSummaryText(state, {
allReportsHaveError: false, allReportsHaveError: false,
noBaseInAllReports: true, noBaseInAllReports: true,
areReportsLoading: false, areReportsLoading: false,
...@@ -434,7 +313,7 @@ describe('Security reports getters', () => { ...@@ -434,7 +313,7 @@ describe('Security reports getters', () => {
it('returns is loading text', () => { it('returns is loading text', () => {
expect( expect(
groupedSummaryText(state(), { groupedSummaryText(state, {
allReportsHaveError: false, allReportsHaveError: false,
noBaseInAllReports: false, noBaseInAllReports: false,
areReportsLoading: true, areReportsLoading: true,
...@@ -444,10 +323,8 @@ describe('Security reports getters', () => { ...@@ -444,10 +323,8 @@ describe('Security reports getters', () => {
}); });
it('returns added and fixed text', () => { it('returns added and fixed text', () => {
const newState = state();
expect( expect(
groupedSummaryText(newState, { groupedSummaryText(state, {
allReportsHaveError: false, allReportsHaveError: false,
noBaseInAllReports: false, noBaseInAllReports: false,
areReportsLoading: false, areReportsLoading: false,
...@@ -461,10 +338,8 @@ describe('Security reports getters', () => { ...@@ -461,10 +338,8 @@ describe('Security reports getters', () => {
}); });
it('returns added text', () => { it('returns added text', () => {
const newState = state();
expect( expect(
groupedSummaryText(newState, { groupedSummaryText(state, {
allReportsHaveError: false, allReportsHaveError: false,
noBaseInAllReports: false, noBaseInAllReports: false,
areReportsLoading: false, areReportsLoading: false,
...@@ -477,10 +352,8 @@ describe('Security reports getters', () => { ...@@ -477,10 +352,8 @@ describe('Security reports getters', () => {
}); });
it('returns fixed text', () => { it('returns fixed text', () => {
const newState = state();
expect( expect(
groupedSummaryText(newState, { groupedSummaryText(state, {
allReportsHaveError: false, allReportsHaveError: false,
noBaseInAllReports: false, noBaseInAllReports: false,
areReportsLoading: false, areReportsLoading: false,
...@@ -493,10 +366,8 @@ describe('Security reports getters', () => { ...@@ -493,10 +366,8 @@ describe('Security reports getters', () => {
}); });
it('returns dismissed text', () => { it('returns dismissed text', () => {
const newState = state();
expect( expect(
groupedSummaryText(newState, { groupedSummaryText(state, {
allReportsHaveError: false, allReportsHaveError: false,
noBaseInAllReports: false, noBaseInAllReports: false,
areReportsLoading: false, areReportsLoading: false,
...@@ -508,10 +379,8 @@ describe('Security reports getters', () => { ...@@ -508,10 +379,8 @@ describe('Security reports getters', () => {
}); });
it('returns added and fixed while loading text', () => { it('returns added and fixed while loading text', () => {
const newState = state();
expect( expect(
groupedSummaryText(newState, { groupedSummaryText(state, {
allReportsHaveError: false, allReportsHaveError: false,
noBaseInAllReports: false, noBaseInAllReports: false,
areReportsLoading: true, areReportsLoading: true,
...@@ -525,10 +394,8 @@ describe('Security reports getters', () => { ...@@ -525,10 +394,8 @@ describe('Security reports getters', () => {
}); });
it('returns no new text if there are existing ones', () => { it('returns no new text if there are existing ones', () => {
const newState = state();
expect( expect(
groupedSummaryText(newState, { groupedSummaryText(state, {
allReportsHaveError: false, allReportsHaveError: false,
noBaseInAllReports: false, noBaseInAllReports: false,
areReportsLoading: false, areReportsLoading: false,
...@@ -540,10 +407,8 @@ describe('Security reports getters', () => { ...@@ -540,10 +407,8 @@ describe('Security reports getters', () => {
}); });
it('returns no text if there are existing ones', () => { it('returns no text if there are existing ones', () => {
const newState = state();
expect( expect(
groupedSummaryText(newState, { groupedSummaryText(state, {
allReportsHaveError: false, allReportsHaveError: false,
noBaseInAllReports: false, noBaseInAllReports: false,
areReportsLoading: false, areReportsLoading: false,
...@@ -553,148 +418,116 @@ describe('Security reports getters', () => { ...@@ -553,148 +418,116 @@ describe('Security reports getters', () => {
}); });
}); });
describe('sastStatusIcon', () => {
it('returns warning with new issues', () => {
const newState = state();
newState.sast.newIssues = [{}];
expect(sastStatusIcon(newState)).toEqual('warning');
});
it('returns warning with failed report', () => {
const newState = state();
newState.sast.hasError = true;
expect(sastStatusIcon(newState)).toEqual('warning');
});
it('returns success with no new issues or failed report', () => {
expect(sastStatusIcon(state())).toEqual('success');
});
});
describe('dastStatusIcon', () => { describe('dastStatusIcon', () => {
it('returns warning with new issues', () => { it('returns warning with new issues', () => {
const newState = state(); state.dast.newIssues = [{}];
newState.dast.newIssues = [{}];
expect(dastStatusIcon(newState)).toEqual('warning'); expect(dastStatusIcon(state)).toEqual('warning');
}); });
it('returns warning with failed report', () => { it('returns warning with failed report', () => {
const newState = state(); state.dast.hasError = true;
newState.dast.hasError = true;
expect(dastStatusIcon(newState)).toEqual('warning'); expect(dastStatusIcon(state)).toEqual('warning');
}); });
it('returns success with no new issues or failed report', () => { it('returns success with no new issues or failed report', () => {
expect(dastStatusIcon(state())).toEqual('success'); expect(dastStatusIcon(state)).toEqual('success');
}); });
}); });
describe('sastContainerStatusIcon', () => { describe('sastContainerStatusIcon', () => {
it('returns warning with new issues', () => { it('returns warning with new issues', () => {
const newState = state(); state.sastContainer.newIssues = [{}];
newState.sastContainer.newIssues = [{}];
expect(sastContainerStatusIcon(newState)).toEqual('warning'); expect(sastContainerStatusIcon(state)).toEqual('warning');
}); });
it('returns warning with failed report', () => { it('returns warning with failed report', () => {
const newState = state(); state.sastContainer.hasError = true;
newState.sastContainer.hasError = true;
expect(sastContainerStatusIcon(newState)).toEqual('warning'); expect(sastContainerStatusIcon(state)).toEqual('warning');
}); });
it('returns success with no new issues or failed report', () => { it('returns success with no new issues or failed report', () => {
expect(sastContainerStatusIcon(state())).toEqual('success'); expect(sastContainerStatusIcon(state)).toEqual('success');
}); });
}); });
describe('dependencyScanningStatusIcon', () => { describe('dependencyScanningStatusIcon', () => {
it('returns warning with new issues', () => { it('returns warning with new issues', () => {
const newState = state(); state.dependencyScanning.newIssues = [{}];
newState.dependencyScanning.newIssues = [{}];
expect(dependencyScanningStatusIcon(newState)).toEqual('warning'); expect(dependencyScanningStatusIcon(state)).toEqual('warning');
}); });
it('returns warning with failed report', () => { it('returns warning with failed report', () => {
const newState = state(); state.dependencyScanning.hasError = true;
newState.dependencyScanning.hasError = true;
expect(dependencyScanningStatusIcon(newState)).toEqual('warning'); expect(dependencyScanningStatusIcon(state)).toEqual('warning');
}); });
it('returns success with no new issues or failed report', () => { it('returns success with no new issues or failed report', () => {
expect(dependencyScanningStatusIcon(state())).toEqual('success'); expect(dependencyScanningStatusIcon(state)).toEqual('success');
}); });
}); });
describe('areReportsLoading', () => { describe('areReportsLoading', () => {
it('returns true when any report is loading', () => { it('returns true when any report is loading', () => {
const newState = state(); state.dast.isLoading = true;
newState.sast.isLoading = true;
expect(areReportsLoading(newState)).toEqual(true); expect(areReportsLoading(state)).toEqual(true);
}); });
it('returns false when none of the reports are loading', () => { it('returns false when none of the reports are loading', () => {
expect(areReportsLoading(state())).toEqual(false); expect(areReportsLoading(state)).toEqual(false);
}); });
}); });
describe('allReportsHaveError', () => { describe('allReportsHaveError', () => {
it('returns true when all reports have error', () => { it('returns true when all reports have error', () => {
const newState = state(); state.sast.hasError = true;
newState.sast.hasError = true; state.dast.hasError = true;
newState.dast.hasError = true; state.sastContainer.hasError = true;
newState.sastContainer.hasError = true; state.dependencyScanning.hasError = true;
newState.dependencyScanning.hasError = true;
expect(allReportsHaveError(newState)).toEqual(true); expect(allReportsHaveError(state)).toEqual(true);
}); });
it('returns false when none of the reports have error', () => { it('returns false when none of the reports have error', () => {
expect(allReportsHaveError(state())).toEqual(false); expect(allReportsHaveError(state)).toEqual(false);
}); });
it('returns false when one of the reports does not have error', () => { it('returns false when one of the reports does not have error', () => {
const newState = state(); state.dast.hasError = false;
newState.sast.hasError = false; state.sastContainer.hasError = true;
newState.dast.hasError = true; state.dependencyScanning.hasError = true;
newState.sastContainer.hasError = true;
newState.dependencyScanning.hasError = true;
expect(allReportsHaveError(newState)).toEqual(false); expect(allReportsHaveError(state)).toEqual(false);
}); });
}); });
describe('anyReportHasError', () => { describe('anyReportHasError', () => {
it('returns true when any of the reports has error', () => { it('returns true when any of the reports has error', () => {
const newState = state(); state.dast.hasError = true;
newState.sast.hasError = true;
expect(anyReportHasError(newState)).toEqual(true); expect(anyReportHasError(state)).toEqual(true);
}); });
it('returns false when none of the reports has error', () => { it('returns false when none of the reports has error', () => {
expect(anyReportHasError(state())).toEqual(false); expect(anyReportHasError(state)).toEqual(false);
}); });
}); });
describe('noBaseInAllReports', () => { describe('noBaseInAllReports', () => {
it('returns true when none reports have base', () => { it('returns true when none reports have base', () => {
expect(noBaseInAllReports(state())).toEqual(true); expect(noBaseInAllReports(state)).toEqual(true);
}); });
it('returns false when any of the reports has base', () => { it('returns false when any of the reports has base', () => {
const newState = state(); state.dast.paths.base = BASE_PATH;
newState.sast.paths.base = BASE_PATH;
expect(noBaseInAllReports(newState)).toEqual(false); expect(noBaseInAllReports(state)).toEqual(false);
}); });
}); });
}); });
import * as types from 'ee/vue_shared/security_reports/store/mutation_types';
import configureMediator from 'ee/vue_shared/security_reports/store/mediator';
const mockedStore = {
dispatch: jest.fn(),
};
mockedStore.subscribe = callback => {
mockedStore.commit = callback;
};
describe('security reports mediator', () => {
beforeEach(() => {
configureMediator(mockedStore);
});
describe(types.RECEIVE_DISMISS_VULNERABILITY_SUCCESS, () => {
const type = types.RECEIVE_DISMISS_VULNERABILITY_SUCCESS;
it.each`
action | category
${'sast/updateVulnerability'} | ${'sast'}
${'updateDastIssue'} | ${'dast'}
${'updateDependencyScanningIssue'} | ${'dependency_scanning'}
${'updateContainerScanningIssue'} | ${'container_scanning'}
`(`should trigger $action on when a $category is updated`, data => {
const { action, category } = data;
const payload = { category };
mockedStore.commit({ type, payload });
expect(mockedStore.dispatch).toHaveBeenCalledWith(action, payload);
});
});
});
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import testAction from 'helpers/vuex_action_helper';
import createState from 'ee/vue_shared/security_reports/store/modules/sast/state';
import * as types from 'ee/vue_shared/security_reports/store/modules/sast/mutation_types';
import * as actions from 'ee/vue_shared/security_reports/store/modules/sast/actions';
const headPath = 'head-path.json';
const basePath = 'base-path.json';
const blobPath = 'blob-path.json';
const reports = {
base: 'base',
head: 'head',
enrichData: 'enrichData',
};
const error = 'Something went wrong';
const issue = {};
const vulnerabilityFeedbackPath = 'vulnerability-feedback-path';
const rootState = { vulnerabilityFeedbackPath, blobPath };
let state;
describe('sast report actions', () => {
beforeEach(() => {
state = createState();
});
describe('setHeadPath', () => {
it(`should commit ${types.SET_HEAD_PATH} with the correct path`, done => {
testAction(
actions.setHeadPath,
headPath,
state,
[
{
type: types.SET_HEAD_PATH,
payload: headPath,
},
],
[],
done,
);
});
});
describe('setBasePath', () => {
it(`should commit ${types.SET_BASE_PATH} with the correct path`, done => {
testAction(
actions.setBasePath,
basePath,
state,
[
{
type: types.SET_BASE_PATH,
payload: basePath,
},
],
[],
done,
);
});
});
describe('requestReports', () => {
it(`should commit ${types.REQUEST_REPORTS}`, done => {
testAction(actions.requestReports, {}, state, [{ type: types.REQUEST_REPORTS }], [], done);
});
});
describe('receiveReports', () => {
it(`should commit ${types.RECEIVE_REPORTS} with the correct response`, done => {
testAction(
actions.receiveReports,
reports,
state,
[
{
type: types.RECEIVE_REPORTS,
payload: reports,
},
],
[],
done,
);
});
});
describe('receiveError', () => {
it(`should commit ${types.RECEIVE_REPORTS_ERROR} with the correct response`, done => {
testAction(
actions.receiveError,
error,
state,
[
{
type: types.RECEIVE_REPORTS_ERROR,
payload: error,
},
],
[],
done,
);
});
});
describe('fetchReports', () => {
let mock;
beforeEach(() => {
mock = new MockAdapter(axios);
state.paths.head = headPath;
state.paths.base = basePath;
});
afterEach(() => {
mock.restore();
});
describe('when everything goes according to plan', () => {
beforeEach(() => {
mock
.onGet(headPath)
.replyOnce(200, reports.head)
.onGet(basePath)
.replyOnce(200, reports.base)
.onGet(vulnerabilityFeedbackPath)
.replyOnce(200, reports.enrichData);
});
it('should dispatch the `receiveReports` action', done => {
testAction(
actions.fetchReports,
{},
{ ...rootState, ...state },
[],
[
{ type: 'requestReports' },
{
type: 'receiveReports',
payload: {
blobPath,
reports,
},
},
],
done,
);
});
});
describe('when the vulnerability feedback endpoint fails', () => {
beforeEach(() => {
mock
.onGet(headPath)
.replyOnce(200, reports.head)
.onGet(basePath)
.replyOnce(200, reports.base)
.onGet(vulnerabilityFeedbackPath)
.replyOnce(404);
});
it('should dispatch the `receiveError` action', done => {
testAction(
actions.fetchReports,
{},
{ ...rootState, ...state },
[],
[{ type: 'requestReports' }, { type: 'receiveError' }],
done,
);
});
});
});
describe('updateVulnerability', () => {
it(`should commit ${types.UPDATE_VULNERABILITY} with the correct response`, done => {
testAction(
actions.updateVulnerability,
issue,
state,
[
{
type: types.UPDATE_VULNERABILITY,
payload: issue,
},
],
[],
done,
);
});
});
});
import {
SAST_HAS_ERROR,
SAST_IS_LOADING,
} from 'ee/vue_shared/security_reports/store/modules/sast/constants';
import * as getters from 'ee/vue_shared/security_reports/store/modules/sast/getters';
const createReport = (config = {}) => ({
paths: [],
newIssues: [],
...config,
});
describe('groupedSastText', () => {
it("should return the error message if there's an error", () => {
const sast = createReport({ hasError: true });
const result = getters.groupedSastText(sast);
expect(result).toBe(SAST_HAS_ERROR);
});
it("should return the loading message if it's still loading", () => {
const sast = createReport({ isLoading: true });
const result = getters.groupedSastText(sast);
expect(result).toBe(SAST_IS_LOADING);
});
it('should call groupedTextBuilder if everything is fine', () => {
const sast = createReport();
const result = getters.groupedSastText(sast);
expect(result).toBe('SAST detected no vulnerabilities for the source branch only');
});
});
describe('sastStatusIcon', () => {
it("should return `loading` when we're still loading", () => {
const sast = createReport({ isLoading: true });
const result = getters.sastStatusIcon(sast);
expect(result).toBe('loading');
});
it("should return `warning` when there's an issue", () => {
const sast = createReport({ hasError: true });
const result = getters.sastStatusIcon(sast);
expect(result).toBe('warning');
});
it('should return `success` when nothing is wrong', () => {
const sast = createReport();
const result = getters.sastStatusIcon(sast);
expect(result).toBe('success');
});
});
import * as types from 'ee/vue_shared/security_reports/store/modules/sast/mutation_types';
import createState from 'ee/vue_shared/security_reports/store/modules/sast/state';
import mutations from 'ee/vue_shared/security_reports/store/modules/sast/mutations';
const createIssue = ({ ...config }) => ({ changed: false, ...config });
describe('sast module mutations', () => {
const path = 'path';
let state;
beforeEach(() => {
state = createState();
});
describe(types.SET_HEAD_PATH, () => {
it('should set the SAST head path', () => {
mutations[types.SET_HEAD_PATH](state, path);
expect(state.paths.head).toBe(path);
});
});
describe(types.SET_BASE_PATH, () => {
it('should set the SAST base path', () => {
mutations[types.SET_BASE_PATH](state, path);
expect(state.paths.base).toBe(path);
});
});
describe(types.REQUEST_REPORTS, () => {
it('should set the `isLoading` status to `true`', () => {
mutations[types.REQUEST_REPORTS](state);
expect(state.isLoading).toBe(true);
});
});
describe(types.RECEIVE_REPORTS_ERROR, () => {
beforeEach(() => {
state.isLoading = true;
mutations[types.RECEIVE_REPORTS_ERROR](state);
});
it('should set the `isLoading` status to `false`', () => {
expect(state.isLoading).toBe(false);
});
it('should set the `hasError` status to `true`', () => {
expect(state.hasError).toBe(true);
});
});
describe(types.UPDATE_VULNERABILITY, () => {
let newIssue;
let resolvedIssue;
let allIssue;
beforeEach(() => {
newIssue = createIssue({ project_fingerprint: 'new' });
resolvedIssue = createIssue({ project_fingerprint: 'resolved' });
allIssue = createIssue({ project_fingerprint: 'all' });
state.newIssues.push(newIssue);
state.resolvedIssues.push(resolvedIssue);
state.allIssues.push(allIssue);
});
describe('with a `new` issue', () => {
beforeEach(() => {
mutations[types.UPDATE_VULNERABILITY](state, { ...newIssue, changed: true });
});
it('should update the correct issue', () => {
expect(state.newIssues[0].changed).toBe(true);
});
});
describe('with a `resolved` issue', () => {
beforeEach(() => {
mutations[types.UPDATE_VULNERABILITY](state, { ...resolvedIssue, changed: true });
});
it('should update the correct issue', () => {
expect(state.resolvedIssues[0].changed).toBe(true);
});
});
describe('with an `all` issue', () => {
beforeEach(() => {
mutations[types.UPDATE_VULNERABILITY](state, { ...allIssue, changed: true });
});
it('should update the correct issue', () => {
expect(state.allIssues[0].changed).toBe(true);
});
});
describe('with an invalid issue', () => {
beforeEach(() => {
mutations[types.UPDATE_VULNERABILITY](
state,
createIssue({ project_fingerprint: 'invalid', changed: true }),
);
});
it('should ignore the issue', () => {
expect(state.newIssues[0].changed).toBe(false);
expect(state.resolvedIssues[0].changed).toBe(false);
expect(state.allIssues[0].changed).toBe(false);
});
});
});
describe(types.RECEIVE_REPORTS, () => {
const head = [
createIssue({ cve: 'CVE-1' }),
createIssue({ cve: 'CVE-4' }),
createIssue({ cve: 'CVE-5' }),
createIssue({ cve: 'CVE-6' }),
];
const base = [
createIssue({ cve: 'CVE-1' }),
createIssue({ cve: 'CVE-2' }),
createIssue({ cve: 'CVE-3' }),
];
const enrichData = [];
const blobPath = 'blobPath';
beforeEach(() => {
state.isLoading = true;
});
describe('with only the head report', () => {
beforeEach(() => {
const reports = { head, enrichData };
mutations[types.RECEIVE_REPORTS](state, { reports, blobPath });
});
it('should set the `isLoading` status to `false`', () => {
expect(state.isLoading).toBe(false);
});
it('should have the relevant `new` issues', () => {
expect(state.newIssues.length).toBe(4);
});
it('should not have any `resolved` issues', () => {
expect(state.resolvedIssues.length).toBe(0);
});
it('should not have any `all` issues', () => {
expect(state.allIssues.length).toBe(0);
});
});
describe('with the base and head reports', () => {
beforeEach(() => {
const reports = { head, base, enrichData };
mutations[types.RECEIVE_REPORTS](state, { reports, blobPath });
});
it('should set the `isLoading` status to `false`', () => {
expect(state.isLoading).toBe(false);
});
it('should have the relevant `new` issues', () => {
expect(state.newIssues.length).toBe(3);
});
it('should have the relevant `resolved` issues', () => {
expect(state.resolvedIssues.length).toBe(2);
});
it('should have the relevant `all` issues', () => {
expect(state.allIssues.length).toBe(1);
});
});
});
});
...@@ -2,11 +2,6 @@ import state from 'ee/vue_shared/security_reports/store/state'; ...@@ -2,11 +2,6 @@ import state from 'ee/vue_shared/security_reports/store/state';
import mutations from 'ee/vue_shared/security_reports/store/mutations'; import mutations from 'ee/vue_shared/security_reports/store/mutations';
import * as types from 'ee/vue_shared/security_reports/store/mutation_types'; import * as types from 'ee/vue_shared/security_reports/store/mutation_types';
import { import {
sastIssues,
sastIssuesBase,
parsedSastIssuesHead,
parsedSastBaseStore,
parsedSastIssuesStore,
dependencyScanningIssuesOld, dependencyScanningIssuesOld,
dependencyScanningIssuesBase, dependencyScanningIssuesBase,
parsedDependencyScanningIssuesHead, parsedDependencyScanningIssuesHead,
...@@ -91,69 +86,6 @@ describe('security reports mutations', () => { ...@@ -91,69 +86,6 @@ describe('security reports mutations', () => {
}); });
}); });
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', () => { describe('SET_SAST_CONTAINER_HEAD_PATH', () => {
it('should set sast container head path', () => { it('should set sast container head path', () => {
mutations[types.SET_SAST_CONTAINER_HEAD_PATH](stateCopy, 'head_path'); mutations[types.SET_SAST_CONTAINER_HEAD_PATH](stateCopy, 'head_path');
...@@ -646,50 +578,6 @@ describe('security reports mutations', () => { ...@@ -646,50 +578,6 @@ describe('security reports mutations', () => {
}); });
}); });
describe('UPDATE_SAST_ISSUE', () => {
it('updates issue in the new issues list', () => {
stateCopy.sast.newIssues = parsedSastIssuesHead;
stateCopy.sast.resolvedIssues = [];
stateCopy.sast.allIssues = [];
const updatedIssue = {
...parsedSastIssuesHead[0],
foo: 'bar',
};
mutations[types.UPDATE_SAST_ISSUE](stateCopy, updatedIssue);
expect(stateCopy.sast.newIssues[0]).toEqual(updatedIssue);
});
it('updates issue in the resolved issues list', () => {
stateCopy.sast.newIssues = [];
stateCopy.sast.resolvedIssues = parsedSastIssuesHead;
stateCopy.sast.allIssues = [];
const updatedIssue = {
...parsedSastIssuesHead[0],
foo: 'bar',
};
mutations[types.UPDATE_SAST_ISSUE](stateCopy, updatedIssue);
expect(stateCopy.sast.resolvedIssues[0]).toEqual(updatedIssue);
});
it('updates issue in the all issues list', () => {
stateCopy.sast.newIssues = [];
stateCopy.sast.resolvedIssues = [];
stateCopy.sast.allIssues = parsedSastIssuesHead;
const updatedIssue = {
...parsedSastIssuesHead[0],
foo: 'bar',
};
mutations[types.UPDATE_SAST_ISSUE](stateCopy, updatedIssue);
expect(stateCopy.sast.allIssues[0]).toEqual(updatedIssue);
});
});
describe('UPDATE_DEPENDENCY_SCANNING_ISSUE', () => { describe('UPDATE_DEPENDENCY_SCANNING_ISSUE', () => {
it('updates issue in the new issues list', () => { it('updates issue in the new issues list', () => {
stateCopy.dependencyScanning.newIssues = parsedDependencyScanningIssuesHead; stateCopy.dependencyScanning.newIssues = parsedDependencyScanningIssuesHead;
......
...@@ -10,6 +10,7 @@ import { ...@@ -10,6 +10,7 @@ import {
groupedTextBuilder, groupedTextBuilder,
statusIcon, statusIcon,
countIssues, countIssues,
groupedReportText,
} from 'ee/vue_shared/security_reports/store/utils'; } from 'ee/vue_shared/security_reports/store/utils';
import filterByKey from 'ee/vue_shared/security_reports/store/utils/filter_by_key'; import filterByKey from 'ee/vue_shared/security_reports/store/utils/filter_by_key';
import { import {
...@@ -593,4 +594,32 @@ describe('security reports utils', () => { ...@@ -593,4 +594,32 @@ describe('security reports utils', () => {
}); });
}); });
}); });
describe('groupedReportText', () => {
const reportType = 'dummyReport';
const errorMessage = 'Something went wrong';
const loadingMessage = 'The report is still loading';
const baseReport = { paths: [] };
it("should return the error message when there's an error", () => {
const report = { ...baseReport, hasError: true };
const result = groupedReportText(report, reportType, errorMessage, loadingMessage);
expect(result).toBe(errorMessage);
});
it("should return the loading message when it's loading", () => {
const report = { ...baseReport, isLoading: true };
const result = groupedReportText(report, reportType, errorMessage, loadingMessage);
expect(result).toBe(loadingMessage);
});
it("should call groupedTextBuilder if it isn't loading and doesn't have an error", () => {
const report = { ...baseReport };
const result = groupedReportText(report, reportType, errorMessage, loadingMessage);
expect(result).toBe(`${reportType} detected no vulnerabilities for the source branch only`);
});
});
}); });
...@@ -3,7 +3,11 @@ import MockAdapter from 'axios-mock-adapter'; ...@@ -3,7 +3,11 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import component from 'ee/vue_shared/security_reports/grouped_security_reports_app.vue'; import component from 'ee/vue_shared/security_reports/grouped_security_reports_app.vue';
import state from 'ee/vue_shared/security_reports/store/state'; import state from 'ee/vue_shared/security_reports/store/state';
import * as types from 'ee/vue_shared/security_reports/store/mutation_types';
import sastState from 'ee/vue_shared/security_reports/store/modules/sast/state';
import * as sastTypes from 'ee/vue_shared/security_reports/store/modules/sast/mutation_types';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { waitForMutation } from 'spec/helpers/vue_test_utils_helper';
import { trimText } from 'spec/helpers/text_helper'; import { trimText } from 'spec/helpers/text_helper';
import { import {
sastIssues, sastIssues,
...@@ -24,13 +28,16 @@ describe('Grouped security reports app', () => { ...@@ -24,13 +28,16 @@ describe('Grouped security reports app', () => {
}); });
afterEach(() => { afterEach(() => {
vm.$store.replaceState(state()); vm.$store.replaceState({
...state(),
sast: sastState(),
});
vm.$destroy(); vm.$destroy();
mock.restore(); mock.restore();
}); });
describe('with error', () => { describe('with error', () => {
beforeEach(() => { beforeEach(done => {
mock.onGet('sast_head.json').reply(500); mock.onGet('sast_head.json').reply(500);
mock.onGet('sast_base.json').reply(500); mock.onGet('sast_base.json').reply(500);
mock.onGet('dast_head.json').reply(500); mock.onGet('dast_head.json').reply(500);
...@@ -63,26 +70,32 @@ describe('Grouped security reports app', () => { ...@@ -63,26 +70,32 @@ describe('Grouped security reports app', () => {
canCreateMergeRequest: true, canCreateMergeRequest: true,
canDismissVulnerability: true, canDismissVulnerability: true,
}); });
Promise.all([
waitForMutation(vm.$store, `sast/${sastTypes.RECEIVE_REPORTS_ERROR}`),
waitForMutation(vm.$store, types.RECEIVE_SAST_CONTAINER_ERROR),
waitForMutation(vm.$store, types.RECEIVE_DAST_ERROR),
waitForMutation(vm.$store, types.RECEIVE_DEPENDENCY_SCANNING_ERROR),
])
.then(done)
.catch();
}); });
it('renders loading state', done => { it('renders error state', () => {
setTimeout(() => { expect(vm.$el.querySelector('.fa-spinner')).toBeNull();
expect(vm.$el.querySelector('.fa-spinner')).toBeNull(); expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual( 'Security scanning failed loading any results',
'Security scanning failed loading any results', );
);
expect(vm.$el.querySelector('.js-collapse-btn').textContent.trim()).toEqual('Expand'); expect(vm.$el.querySelector('.js-collapse-btn').textContent.trim()).toEqual('Expand');
expect(trimText(vm.$el.textContent)).toContain('SAST: Loading resulted in an error'); expect(trimText(vm.$el.textContent)).toContain('SAST: Loading resulted in an error');
expect(trimText(vm.$el.textContent)).toContain( expect(trimText(vm.$el.textContent)).toContain(
'Dependency scanning: Loading resulted in an error', 'Dependency scanning: Loading resulted in an error',
); );
expect(vm.$el.textContent).toContain('Container scanning: Loading resulted in an error'); expect(vm.$el.textContent).toContain('Container scanning: Loading resulted in an error');
expect(vm.$el.textContent).toContain('DAST: Loading resulted in an error'); expect(vm.$el.textContent).toContain('DAST: Loading resulted in an error');
done();
}, 0);
}); });
}); });
...@@ -122,7 +135,7 @@ describe('Grouped security reports app', () => { ...@@ -122,7 +135,7 @@ describe('Grouped security reports app', () => {
}); });
}); });
it('renders loading summary text + spinner', done => { it('renders loading summary text + spinner', () => {
expect(vm.$el.querySelector('.spinner')).not.toBeNull(); expect(vm.$el.querySelector('.spinner')).not.toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual( expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Security scanning is loading', 'Security scanning is loading',
...@@ -134,15 +147,11 @@ describe('Grouped security reports app', () => { ...@@ -134,15 +147,11 @@ describe('Grouped security reports app', () => {
expect(vm.$el.textContent).toContain('Dependency scanning is loading'); expect(vm.$el.textContent).toContain('Dependency scanning is loading');
expect(vm.$el.textContent).toContain('Container scanning is loading'); expect(vm.$el.textContent).toContain('Container scanning is loading');
expect(vm.$el.textContent).toContain('DAST is loading'); expect(vm.$el.textContent).toContain('DAST is loading');
setTimeout(() => {
done();
}, 0);
}); });
}); });
describe('with all reports', () => { describe('with all reports', () => {
beforeEach(() => { beforeEach(done => {
mock.onGet('sast_head.json').reply(200, sastIssues); mock.onGet('sast_head.json').reply(200, sastIssues);
mock.onGet('sast_base.json').reply(200, sastIssuesBase); mock.onGet('sast_base.json').reply(200, sastIssuesBase);
mock.onGet('dast_head.json').reply(200, dast); mock.onGet('dast_head.json').reply(200, dast);
...@@ -175,40 +184,46 @@ describe('Grouped security reports app', () => { ...@@ -175,40 +184,46 @@ describe('Grouped security reports app', () => {
canCreateMergeRequest: true, canCreateMergeRequest: true,
canDismissVulnerability: true, canDismissVulnerability: true,
}); });
Promise.all([
waitForMutation(vm.$store, `sast/${sastTypes.RECEIVE_REPORTS}`),
waitForMutation(vm.$store, types.RECEIVE_DAST_REPORTS),
waitForMutation(vm.$store, types.RECEIVE_SAST_CONTAINER_REPORTS),
waitForMutation(vm.$store, types.RECEIVE_DEPENDENCY_SCANNING_REPORTS),
])
.then(done)
.catch();
}); });
it('renders reports', done => { it('renders reports', () => {
setTimeout(() => { // It's not loading
// It's not loading expect(vm.$el.querySelector('.fa-spinner')).toBeNull();
expect(vm.$el.querySelector('.fa-spinner')).toBeNull();
// Renders the summary text // Renders the summary text
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual( expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Security scanning detected 6 new, and 3 fixed vulnerabilities', 'Security scanning detected 6 new, and 3 fixed vulnerabilities',
); );
// Renders the expand button // Renders the expand button
expect(vm.$el.querySelector('.js-collapse-btn').textContent.trim()).toEqual('Expand'); expect(vm.$el.querySelector('.js-collapse-btn').textContent.trim()).toEqual('Expand');
// Renders Sast result // Renders Sast result
expect(trimText(vm.$el.textContent)).toContain( expect(trimText(vm.$el.textContent)).toContain(
'SAST detected 2 new, and 1 fixed vulnerabilities', 'SAST detected 2 new, and 1 fixed vulnerabilities',
); );
// Renders DSS result // Renders DSS result
expect(trimText(vm.$el.textContent)).toContain( expect(trimText(vm.$el.textContent)).toContain(
'Dependency scanning detected 2 new, and 1 fixed vulnerabilities', 'Dependency scanning detected 2 new, and 1 fixed vulnerabilities',
); );
// Renders container scanning result // Renders container scanning result
expect(vm.$el.textContent).toContain( expect(vm.$el.textContent).toContain(
'Container scanning detected 1 new, and 1 fixed vulnerabilities', 'Container scanning detected 1 new, and 1 fixed vulnerabilities',
); );
// Renders DAST result // Renders DAST result
expect(vm.$el.textContent).toContain('DAST detected 1 new vulnerability'); expect(vm.$el.textContent).toContain('DAST detected 1 new vulnerability');
done();
}, 0);
}); });
it('opens modal with more information', done => { it('opens modal with more information', done => {
......
...@@ -8,12 +8,6 @@ import actions, { ...@@ -8,12 +8,6 @@ import actions, {
setPipelineId, setPipelineId,
setCanCreateIssuePermission, setCanCreateIssuePermission,
setCanCreateFeedbackPermission, setCanCreateFeedbackPermission,
setSastHeadPath,
setSastBasePath,
requestSastReports,
receiveSastReports,
receiveSastError,
fetchSastReports,
setSastContainerHeadPath, setSastContainerHeadPath,
setSastContainerBasePath, setSastContainerBasePath,
requestSastContainerReports, requestSastContainerReports,
...@@ -48,7 +42,6 @@ import actions, { ...@@ -48,7 +42,6 @@ import actions, {
receiveCreateMergeRequestSuccess, receiveCreateMergeRequestSuccess,
receiveCreateMergeRequestError, receiveCreateMergeRequestError,
createMergeRequest, createMergeRequest,
updateSastIssue,
updateDependencyScanningIssue, updateDependencyScanningIssue,
updateContainerScanningIssue, updateContainerScanningIssue,
updateDastIssue, updateDastIssue,
...@@ -72,6 +65,23 @@ import { ...@@ -72,6 +65,23 @@ import {
containerScanningFeedbacks, containerScanningFeedbacks,
} from '../mock_data'; } from '../mock_data';
const createVulnerability = options => ({
...options,
});
const createNonDismissedVulnerability = options =>
createVulnerability({
...options,
isDismissed: false,
dismissalFeedback: null,
});
const createDismissedVulnerability = options =>
createVulnerability({
...options,
isDismissed: true,
});
describe('security reports actions', () => { describe('security reports actions', () => {
let mockedState; let mockedState;
let mock; let mock;
...@@ -211,209 +221,6 @@ describe('security reports actions', () => { ...@@ -211,209 +221,6 @@ describe('security reports actions', () => {
}); });
}); });
describe('setSastHeadPath', () => {
it('should commit set head blob path', done => {
testAction(
setSastHeadPath,
'path',
mockedState,
[
{
type: types.SET_SAST_HEAD_PATH,
payload: 'path',
},
],
[],
done,
);
});
});
describe('setSastBasePath', () => {
it('should commit set head blob path', done => {
testAction(
setSastBasePath,
'path',
mockedState,
[
{
type: types.SET_SAST_BASE_PATH,
payload: 'path',
},
],
[],
done,
);
});
});
describe('requestSastReports', () => {
it('should commit request mutation', done => {
testAction(
requestSastReports,
null,
mockedState,
[
{
type: types.REQUEST_SAST_REPORTS,
},
],
[],
done,
);
});
});
describe('receiveSastReports', () => {
it('should commit request mutation', done => {
testAction(
receiveSastReports,
{},
mockedState,
[
{
type: types.RECEIVE_SAST_REPORTS,
payload: {},
},
],
[],
done,
);
});
});
describe('receiveSastError', () => {
it('should commit sast error mutation', done => {
const error = new Error('test');
testAction(
receiveSastError,
error,
mockedState,
[
{
type: types.RECEIVE_SAST_REPORTS_ERROR,
payload: error,
},
],
[],
done,
);
});
});
describe('fetchSastReports', () => {
describe('with head and base', () => {
it('should dispatch `receiveSastReports`', done => {
mock.onGet('foo').reply(200, sastIssues);
mock.onGet('bar').reply(200, sastIssuesBase);
mock
.onGet('vulnerabilities_path', {
params: {
category: 'sast',
},
})
.reply(200, sastFeedbacks);
mockedState.sast.paths.head = 'foo';
mockedState.sast.paths.base = 'bar';
mockedState.vulnerabilityFeedbackPath = 'vulnerabilities_path';
testAction(
fetchSastReports,
null,
mockedState,
[],
[
{
type: 'requestSastReports',
},
{
type: 'receiveSastReports',
payload: { head: sastIssues, base: sastIssuesBase, enrichData: sastFeedbacks },
},
],
done,
);
});
it('should dispatch `receiveSastError`', done => {
mock.onGet('foo').reply(500, {});
mockedState.sast.paths.head = 'foo';
mockedState.sast.paths.base = 'bar';
testAction(
fetchSastReports,
null,
mockedState,
[],
[
{
type: 'requestSastReports',
},
{
type: 'receiveSastError',
},
],
done,
);
});
});
describe('with head', () => {
it('should dispatch `receiveSastReports`', done => {
mock.onGet('foo').reply(200, sastIssues);
mock
.onGet('vulnerabilities_path', {
params: {
category: 'sast',
},
})
.reply(200, sastFeedbacks);
mockedState.sast.paths.head = 'foo';
mockedState.vulnerabilityFeedbackPath = 'vulnerabilities_path';
testAction(
fetchSastReports,
null,
mockedState,
[],
[
{
type: 'requestSastReports',
},
{
type: 'receiveSastReports',
payload: { head: sastIssues, base: null, enrichData: sastFeedbacks },
},
],
done,
);
});
it('should dispatch `receiveSastError`', done => {
mock.onGet('foo').reply(500, {});
mockedState.sast.paths.head = 'foo';
testAction(
fetchSastReports,
null,
mockedState,
[],
[
{
type: 'requestSastReports',
},
{
type: 'receiveSastError',
},
],
done,
);
});
});
});
describe('setSastContainerHeadPath', () => { describe('setSastContainerHeadPath', () => {
it('should commit set head blob path', done => { it('should commit set head blob path', done => {
testAction( testAction(
...@@ -600,7 +407,7 @@ describe('security reports actions', () => { ...@@ -600,7 +407,7 @@ describe('security reports actions', () => {
); );
}); });
it('should dispatch `receiveSastError`', done => { it('should dispatch `receiveSastContainerError`', done => {
mock.onGet('foo').reply(500, {}); mock.onGet('foo').reply(500, {});
mockedState.sastContainer.paths.head = 'foo'; mockedState.sastContainer.paths.head = 'foo';
...@@ -805,7 +612,7 @@ describe('security reports actions', () => { ...@@ -805,7 +612,7 @@ describe('security reports actions', () => {
); );
}); });
it('should dispatch `receiveSastError`', done => { it('should dispatch `receiveDastError`', done => {
mock.onGet('foo').reply(500, {}); mock.onGet('foo').reply(500, {});
mockedState.dast.paths.head = 'foo'; mockedState.dast.paths.head = 'foo';
...@@ -900,7 +707,7 @@ describe('security reports actions', () => { ...@@ -900,7 +707,7 @@ describe('security reports actions', () => {
}); });
describe('receiveDependencyScanningError', () => { describe('receiveDependencyScanningError', () => {
it('should commit sast error mutation', done => { it('should commit dependency scanning error mutation', done => {
const error = new Error('test'); const error = new Error('test');
testAction( testAction(
...@@ -1086,14 +893,17 @@ describe('security reports actions', () => { ...@@ -1086,14 +893,17 @@ describe('security reports actions', () => {
}); });
describe('receiveDismissVulnerability', () => { describe('receiveDismissVulnerability', () => {
it('commits receive dismiss issue', done => { it(`should pass the payload to the ${types.RECEIVE_DISMISS_VULNERABILITY_SUCCESS} mutation`, done => {
const payload = createDismissedVulnerability();
testAction( testAction(
receiveDismissVulnerability, receiveDismissVulnerability,
null, payload,
mockedState, mockedState,
[ [
{ {
type: types.RECEIVE_DISMISS_VULNERABILITY_SUCCESS, type: types.RECEIVE_DISMISS_VULNERABILITY_SUCCESS,
payload,
}, },
], ],
[], [],
...@@ -1122,143 +932,25 @@ describe('security reports actions', () => { ...@@ -1122,143 +932,25 @@ describe('security reports actions', () => {
describe('dismissVulnerability', () => { describe('dismissVulnerability', () => {
describe('with success', () => { describe('with success', () => {
let payload;
let dismissalFeedback; let dismissalFeedback;
beforeEach(() => { beforeEach(() => {
dismissalFeedback = { dismissalFeedback = {
foo: 'bar', foo: 'bar',
}; };
mock.onPost('dismiss_vulnerability_path').reply(200, dismissalFeedback); payload = createDismissedVulnerability({
mockedState.createVulnerabilityFeedbackDismissalPath = 'dismiss_vulnerability_path';
});
it('with success should dispatch `receiveDismissVulnerability`', done => {
testAction(
dismissVulnerability,
null,
mockedState,
[],
[
{
type: 'requestDismissVulnerability',
},
{
type: 'closeDismissalCommentBox',
},
{
type: 'receiveDismissVulnerability',
},
],
done,
);
});
it('should dispatch `updateSastIssue` for sast issue', done => {
mockedState.modal.vulnerability.category = 'sast';
const expectedUpdatePayload = {
...mockedState.modal.vulnerability,
isDismissed: true,
dismissalFeedback,
};
testAction(
dismissVulnerability,
null,
mockedState,
[],
[
{
type: 'requestDismissVulnerability',
},
{
type: 'closeDismissalCommentBox',
},
{
type: 'receiveDismissVulnerability',
},
{
type: 'updateSastIssue',
payload: expectedUpdatePayload,
},
],
done,
);
});
it('should dispatch `updateDependencyScanningIssue` for dependency scanning issue', done => {
mockedState.modal.vulnerability.category = 'dependency_scanning';
const expectedUpdatePayload = {
...mockedState.modal.vulnerability,
isDismissed: true,
dismissalFeedback,
};
testAction(
dismissVulnerability,
null,
mockedState,
[],
[
{
type: 'requestDismissVulnerability',
},
{
type: 'closeDismissalCommentBox',
},
{
type: 'receiveDismissVulnerability',
},
{
type: 'updateDependencyScanningIssue',
payload: expectedUpdatePayload,
},
],
done,
);
});
it('should dispatch `updateContainerScanningIssue` for container scanning issue', done => {
mockedState.modal.vulnerability.category = 'container_scanning';
const expectedUpdatePayload = {
...mockedState.modal.vulnerability, ...mockedState.modal.vulnerability,
isDismissed: true,
dismissalFeedback, dismissalFeedback,
}; });
mock.onPost('dismiss_vulnerability_path').reply(200, dismissalFeedback);
testAction( mockedState.createVulnerabilityFeedbackDismissalPath = 'dismiss_vulnerability_path';
dismissVulnerability,
null,
mockedState,
[],
[
{
type: 'requestDismissVulnerability',
},
{
type: 'closeDismissalCommentBox',
},
{
type: 'receiveDismissVulnerability',
},
{
type: 'updateContainerScanningIssue',
payload: expectedUpdatePayload,
},
],
done,
);
}); });
it('should dispatch `updateDastIssue` for dast issue', done => { it(`should dispatch ${types.receiveDismissVulnerability}`, done => {
mockedState.modal.vulnerability.category = 'dast';
const expectedUpdatePayload = {
...mockedState.modal.vulnerability,
isDismissed: true,
dismissalFeedback,
};
testAction( testAction(
dismissVulnerability, dismissVulnerability,
null, payload,
mockedState, mockedState,
[], [],
[ [
...@@ -1270,10 +962,7 @@ describe('security reports actions', () => { ...@@ -1270,10 +962,7 @@ describe('security reports actions', () => {
}, },
{ {
type: 'receiveDismissVulnerability', type: 'receiveDismissVulnerability',
}, payload,
{
type: 'updateDastIssue',
payload: expectedUpdatePayload,
}, },
], ],
done, done,
...@@ -1408,18 +1097,21 @@ describe('security reports actions', () => { ...@@ -1408,18 +1097,21 @@ describe('security reports actions', () => {
describe('revertDismissVulnerability', () => { describe('revertDismissVulnerability', () => {
describe('with success', () => { describe('with success', () => {
let payload;
beforeEach(() => { beforeEach(() => {
mock.onDelete('dismiss_vulnerability_path/123').reply(200, {}); mock.onDelete('dismiss_vulnerability_path/123').reply(200, {});
mockedState.modal.vulnerability.dismissalFeedback = { mockedState.modal.vulnerability.dismissalFeedback = {
id: 123, id: 123,
destroy_vulnerability_feedback_dismissal_path: 'dismiss_vulnerability_path/123', destroy_vulnerability_feedback_dismissal_path: 'dismiss_vulnerability_path/123',
}; };
payload = createNonDismissedVulnerability({ ...mockedState.modal.vulnerability });
}); });
it('should dispatch `receiveDismissVulnerability`', done => { it('should dispatch `receiveDismissVulnerability`', done => {
testAction( testAction(
revertDismissVulnerability, revertDismissVulnerability,
null, payload,
mockedState, mockedState,
[], [],
[ [
...@@ -1428,122 +1120,7 @@ describe('security reports actions', () => { ...@@ -1428,122 +1120,7 @@ describe('security reports actions', () => {
}, },
{ {
type: 'receiveDismissVulnerability', type: 'receiveDismissVulnerability',
}, payload,
],
done,
);
});
it('should dispatch `updateSastIssue` for sast issue', done => {
mockedState.modal.vulnerability.category = 'sast';
const expectedUpdatePayload = {
...mockedState.modal.vulnerability,
isDismissed: false,
dismissalFeedback: null,
};
testAction(
revertDismissVulnerability,
null,
mockedState,
[],
[
{
type: 'requestDismissVulnerability',
},
{
type: 'receiveDismissVulnerability',
},
{
type: 'updateSastIssue',
payload: expectedUpdatePayload,
},
],
done,
);
});
it('should dispatch `updateDependencyScanningIssue` for dependency scanning issue', done => {
mockedState.modal.vulnerability.category = 'dependency_scanning';
const expectedUpdatePayload = {
...mockedState.modal.vulnerability,
isDismissed: false,
dismissalFeedback: null,
};
testAction(
revertDismissVulnerability,
null,
mockedState,
[],
[
{
type: 'requestDismissVulnerability',
},
{
type: 'receiveDismissVulnerability',
},
{
type: 'updateDependencyScanningIssue',
payload: expectedUpdatePayload,
},
],
done,
);
});
it('should dispatch `updateContainerScanningIssue` for container scanning issue', done => {
mockedState.modal.vulnerability.category = 'container_scanning';
const expectedUpdatePayload = {
...mockedState.modal.vulnerability,
isDismissed: false,
dismissalFeedback: null,
};
testAction(
revertDismissVulnerability,
null,
mockedState,
[],
[
{
type: 'requestDismissVulnerability',
},
{
type: 'receiveDismissVulnerability',
},
{
type: 'updateContainerScanningIssue',
payload: expectedUpdatePayload,
},
],
done,
);
});
it('should dispatch `updateDastIssue` for dast issue', done => {
mockedState.modal.vulnerability.category = 'dast';
const expectedUpdatePayload = {
...mockedState.modal.vulnerability,
isDismissed: false,
dismissalFeedback: null,
};
testAction(
revertDismissVulnerability,
null,
mockedState,
[],
[
{
type: 'requestDismissVulnerability',
},
{
type: 'receiveDismissVulnerability',
},
{
type: 'updateDastIssue',
payload: expectedUpdatePayload,
}, },
], ],
done, done,
...@@ -1632,7 +1209,7 @@ describe('security reports actions', () => { ...@@ -1632,7 +1209,7 @@ describe('security reports actions', () => {
spyOnDependency(actions, 'visitUrl'); spyOnDependency(actions, 'visitUrl');
}); });
it('with success should dispatch `receiveDismissVulnerability`', done => { it('with success should dispatch `requestCreateIssue` and `receiveCreateIssue`', done => {
mock.onPost('create_issue_path').reply(200, { issue_path: 'new_issue' }); mock.onPost('create_issue_path').reply(200, { issue_path: 'new_issue' });
mockedState.createVulnerabilityFeedbackIssuePath = 'create_issue_path'; mockedState.createVulnerabilityFeedbackIssuePath = 'create_issue_path';
...@@ -1807,26 +1384,6 @@ describe('security reports actions', () => { ...@@ -1807,26 +1384,6 @@ describe('security reports actions', () => {
}); });
}); });
describe('updateSastIssue', () => {
it('commits update sast issue', done => {
const payload = { foo: 'bar' };
testAction(
updateSastIssue,
payload,
mockedState,
[
{
type: types.UPDATE_SAST_ISSUE,
payload,
},
],
[],
done,
);
});
});
describe('updateDependencyScanningIssue', () => { describe('updateDependencyScanningIssue', () => {
it('commits update dependency scanning issue', done => { it('commits update dependency scanning issue', done => {
const payload = { foo: 'bar' }; const payload = { foo: 'bar' };
......
/* eslint-disable import/prefer-default-export */
const vNodeContainsText = (vnode, text) => const vNodeContainsText = (vnode, text) =>
(vnode.text && vnode.text.includes(text)) || (vnode.text && vnode.text.includes(text)) ||
(vnode.children && vnode.children.filter(child => vNodeContainsText(child, text)).length); (vnode.children && vnode.children.filter(child => vNodeContainsText(child, text)).length);
...@@ -19,3 +17,19 @@ export const shallowWrapperContainsSlotText = (shallowWrapper, slotName, text) = ...@@ -19,3 +17,19 @@ export const shallowWrapperContainsSlotText = (shallowWrapper, slotName, text) =
Boolean( Boolean(
shallowWrapper.vm.$slots[slotName].filter(vnode => vNodeContainsText(vnode, text)).length, shallowWrapper.vm.$slots[slotName].filter(vnode => vNodeContainsText(vnode, text)).length,
); );
/**
* Returns a promise that waits for a mutation to be fired before resolving
* NOTE: There's no reject action here so it will hang if it waits for a mutation that won't happen.
* @param {Object} store - The Vue store that contains the mutations
* @param {String} expectedMutationType - The Mutation to wait for
*/
export const waitForMutation = (store, expectedMutationType) =>
new Promise(resolve => {
const unsubscribe = store.subscribe(mutation => {
if (mutation.type === expectedMutationType) {
unsubscribe();
resolve();
}
});
});
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