Commit c4eb1b3f authored by David O'Regan's avatar David O'Regan

Merge branch '273423-implement-ce-vuex-store' into 'master'

Implement CE security_reports Vuex store

See merge request gitlab-org/gitlab!47556
parents c39c1546 4c4f0962
......@@ -18,9 +18,9 @@ export const ICON_SUCCESS = 'success';
export const ICON_NOTFOUND = 'notfound';
export const status = {
LOADING: 'LOADING',
ERROR: 'ERROR',
SUCCESS: 'SUCCESS',
LOADING,
ERROR,
SUCCESS,
};
export const ACCESSIBILITY_ISSUE_ERROR = 'error';
......
/**
* Vuex module names corresponding to security scan types. These are similar to
* the snake_case report types from the backend, but should not be considered
* to be equivalent.
*/
export const MODULE_SAST = 'sast';
export const MODULE_SECRET_DETECTION = 'secretDetection';
import { s__, sprintf } from '~/locale';
import { countVulnerabilities, groupedTextBuilder } from './utils';
import { LOADING, ERROR, SUCCESS } from '~/reports/constants';
import { TRANSLATION_IS_LOADING } from './messages';
export const summaryCounts = state =>
countVulnerabilities(
state.reportTypes.reduce((acc, reportType) => {
acc.push(...state[reportType].newIssues);
return acc;
}, []),
);
export const groupedSummaryText = (state, getters) => {
const reportType = s__('ciReport|Security scanning');
let status = '';
// All reports are loading
if (getters.areAllReportsLoading) {
return { message: sprintf(TRANSLATION_IS_LOADING, { reportType }) };
}
// All reports returned error
if (getters.allReportsHaveError) {
return { message: s__('ciReport|Security scanning failed loading any results') };
}
if (getters.areReportsLoading && getters.anyReportHasError) {
status = s__('ciReport|is loading, errors when loading results');
} else if (getters.areReportsLoading && !getters.anyReportHasError) {
status = s__('ciReport|is loading');
} else if (!getters.areReportsLoading && getters.anyReportHasError) {
status = s__('ciReport|: Loading resulted in an error');
}
const { critical, high, other } = getters.summaryCounts;
return groupedTextBuilder({ reportType, status, critical, high, other });
};
export const summaryStatus = (state, getters) => {
if (getters.areReportsLoading) {
return LOADING;
}
if (getters.anyReportHasError || getters.anyReportHasIssues) {
return ERROR;
}
return SUCCESS;
};
export const areReportsLoading = state =>
state.reportTypes.some(reportType => state[reportType].isLoading);
export const areAllReportsLoading = state =>
state.reportTypes.every(reportType => state[reportType].isLoading);
export const allReportsHaveError = state =>
state.reportTypes.every(reportType => state[reportType].hasError);
export const anyReportHasError = state =>
state.reportTypes.some(reportType => state[reportType].hasError);
export const anyReportHasIssues = state =>
state.reportTypes.some(reportType => state[reportType].newIssues.length > 0);
import Vuex from 'vuex';
import * as getters from './getters';
import state from './state';
import { MODULE_SAST, MODULE_SECRET_DETECTION } from './constants';
import sast from './modules/sast';
import secretDetection from './modules/secret_detection';
export default () =>
new Vuex.Store({
modules: {
[MODULE_SAST]: sast,
[MODULE_SECRET_DETECTION]: secretDetection,
},
getters,
state,
});
import { s__ } from '~/locale';
export const TRANSLATION_IS_LOADING = s__('ciReport|%{reportType} is loading');
export const TRANSLATION_HAS_ERROR = s__('ciReport|%{reportType}: Loading resulted in an error');
import { MODULE_SAST, MODULE_SECRET_DETECTION } from './constants';
export default () => ({
reportTypes: [MODULE_SAST, MODULE_SECRET_DETECTION],
});
import pollUntilComplete from '~/lib/utils/poll_until_complete';
import axios from '~/lib/utils/axios_utils';
import { __, n__, sprintf } from '~/locale';
import { CRITICAL, HIGH } from '~/vulnerabilities/constants';
import {
FEEDBACK_TYPE_DISMISSAL,
FEEDBACK_TYPE_ISSUE,
......@@ -73,3 +75,79 @@ export const parseDiff = (diff, enrichData) => {
existing: diff.existing ? diff.existing.map(enrichVulnerability) : [],
};
};
const createCountMessage = ({ critical, high, other, total }) => {
const otherMessage = n__('%d Other', '%d Others', other);
const countMessage = __(
'%{criticalStart}%{critical} Critical%{criticalEnd} %{highStart}%{high} High%{highEnd} and %{otherStart}%{otherMessage}%{otherEnd}',
);
return total ? sprintf(countMessage, { critical, high, otherMessage }) : '';
};
const createStatusMessage = ({ reportType, status, total }) => {
const vulnMessage = n__('vulnerability', 'vulnerabilities', total);
let message;
if (status) {
message = __('%{reportType} %{status}');
} else if (!total) {
message = __('%{reportType} detected %{totalStart}no%{totalEnd} vulnerabilities.');
} else {
message = __(
'%{reportType} detected %{totalStart}%{total}%{totalEnd} potential %{vulnMessage}',
);
}
return sprintf(message, { reportType, status, total, vulnMessage });
};
/**
* Counts vulnerabilities.
* Returns the amount of critical, high, and other vulnerabilities.
*
* @param {Array} vulnerabilities The raw vulnerabilities to parse
* @returns {{critical: number, high: number, other: number}}
*/
export const countVulnerabilities = (vulnerabilities = []) =>
vulnerabilities.reduce(
(acc, { severity }) => {
if (severity === CRITICAL) {
acc.critical += 1;
} else if (severity === HIGH) {
acc.high += 1;
} else {
acc.other += 1;
}
return acc;
},
{ critical: 0, high: 0, other: 0 },
);
/**
* Takes an object of options and returns the object with an externalized string representing
* the critical, high, and other severity vulnerabilities for a given report.
*
* The resulting string _may_ still contain sprintf-style placeholders. These
* are left in place so they can be replaced with markup, via the
* SecuritySummary component.
* @param {{reportType: string, status: string, critical: number, high: number, other: number}} options
* @returns {Object} the parameters with an externalized string
*/
export const groupedTextBuilder = ({
reportType = '',
status = '',
critical = 0,
high = 0,
other = 0,
} = {}) => {
const total = critical + high + other;
return {
countMessage: createCountMessage({ critical, high, other, total }),
message: createStatusMessage({ reportType, status, total }),
critical,
high,
other,
status,
total,
};
};
/**
* Vulnerability severities as provided by the backend on vulnerability
* objects.
*/
export const CRITICAL = 'critical';
export const HIGH = 'high';
export const MEDIUM = 'medium';
export const LOW = 'low';
export const INFO = 'info';
export const UNKNOWN = 'unknown';
/**
* All vulnerability severities in decreasing order.
*/
export const SEVERITIES = [CRITICAL, HIGH, MEDIUM, LOW, INFO, UNKNOWN];
import httpStatusCodes from '~/lib/utils/http_status';
export const CRITICAL = 'critical';
export const HIGH = 'high';
export const MEDIUM = 'medium';
export const LOW = 'low';
export const INFO = 'info';
export const UNKNOWN = 'unknown';
export const SEVERITIES = [CRITICAL, HIGH, MEDIUM, LOW, INFO, UNKNOWN];
export {
CRITICAL,
HIGH,
MEDIUM,
LOW,
INFO,
UNKNOWN,
SEVERITIES,
} from '~/vulnerabilities/constants';
export const DAYS = {
THIRTY: 30,
......
......@@ -234,6 +234,8 @@ export default {
};
},
},
// TODO: Use the snake_case report types rather than the camelCased versions
// of them. See https://gitlab.com/gitlab-org/gitlab/-/issues/282430
securityReportTypes: [
'dast',
'sast',
......
......@@ -3,7 +3,6 @@ import { mapActions, mapState, mapGetters } from 'vuex';
import { once } from 'lodash';
import { componentNames } from 'ee/reports/components/issue_body';
import { GlButton, GlSprintf, GlLink, GlModalDirective } from '@gitlab/ui';
import { trackMrSecurityReportDetails } from 'ee/vue_shared/security_reports/store/constants';
import FuzzingArtifactsDownload from 'ee/security_dashboard/components/fuzzing_artifacts_download.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import ReportSection from '~/reports/components/report_section.vue';
......@@ -18,6 +17,15 @@ import { mrStates } from '~/mr_popover/constants';
import { fetchPolicies } from '~/lib/graphql';
import securityReportSummaryQuery from './graphql/mr_security_report_summary.graphql';
import SecuritySummary from './components/security_summary.vue';
import {
MODULE_CONTAINER_SCANNING,
MODULE_COVERAGE_FUZZING,
MODULE_DAST,
MODULE_DEPENDENCY_SCANNING,
MODULE_SAST,
MODULE_SECRET_DETECTION,
trackMrSecurityReportDetails,
} from './store/constants';
export default {
store: createStore(),
......@@ -186,12 +194,12 @@ export default {
componentNames,
computed: {
...mapState([
'sast',
'containerScanning',
'dast',
'coverageFuzzing',
'dependencyScanning',
'secretDetection',
MODULE_SAST,
MODULE_CONTAINER_SCANNING,
MODULE_DAST,
MODULE_COVERAGE_FUZZING,
MODULE_DEPENDENCY_SCANNING,
MODULE_SECRET_DETECTION,
'summaryCounts',
'modal',
'isCreatingIssue',
......@@ -214,8 +222,11 @@ export default {
'canCreateMergeRequest',
'canDismissVulnerability',
]),
...mapGetters('sast', ['groupedSastText', 'sastStatusIcon']),
...mapGetters('secretDetection', ['groupedSecretDetectionText', 'secretDetectionStatusIcon']),
...mapGetters(MODULE_SAST, ['groupedSastText', 'sastStatusIcon']),
...mapGetters(MODULE_SECRET_DETECTION, [
'groupedSecretDetectionText',
'secretDetectionStatusIcon',
]),
...mapGetters('pipelineJobs', ['hasFuzzingArtifacts', 'fuzzingJobsWithArtifact']),
securityTab() {
return `${this.pipelinePath}/security`;
......@@ -258,22 +269,22 @@ export default {
return this.dastSummary?.scannedResourcesCsvPath || '';
},
hasCoverageFuzzingIssues() {
return this.hasIssuesForReportType('coverageFuzzing');
return this.hasIssuesForReportType(MODULE_COVERAGE_FUZZING);
},
hasSastIssues() {
return this.hasIssuesForReportType('sast');
return this.hasIssuesForReportType(MODULE_SAST);
},
hasDependencyScanningIssues() {
return this.hasIssuesForReportType('dependencyScanning');
return this.hasIssuesForReportType(MODULE_DEPENDENCY_SCANNING);
},
hasContainerScanningIssues() {
return this.hasIssuesForReportType('containerScanning');
return this.hasIssuesForReportType(MODULE_CONTAINER_SCANNING);
},
hasDastIssues() {
return this.hasIssuesForReportType('dast');
return this.hasIssuesForReportType(MODULE_DAST);
},
hasSecretDetectionIssues() {
return this.hasIssuesForReportType('secretDetection');
return this.hasIssuesForReportType(MODULE_SECRET_DETECTION);
},
},
......@@ -369,11 +380,11 @@ export default {
'fetchCoverageFuzzingDiff',
'setCoverageFuzzingDiffEndpoint',
]),
...mapActions('sast', {
...mapActions(MODULE_SAST, {
setSastDiffEndpoint: 'setDiffEndpoint',
fetchSastDiff: 'fetchDiff',
}),
...mapActions('secretDetection', {
...mapActions(MODULE_SECRET_DETECTION, {
setSecretDetectionDiffEndpoint: 'setDiffEndpoint',
fetchSecretDetectionDiff: 'fetchDiff',
}),
......
import { LOADING, ERROR, SUCCESS } from '../store/constants';
import { LOADING, ERROR, SUCCESS } from '~/reports/constants';
export default {
methods: {
......
export const LOADING = 'LOADING';
export const ERROR = 'ERROR';
export const SUCCESS = 'SUCCESS';
export * from '~/vue_shared/security_reports/store/constants';
/**
* Vuex module names corresponding to security scan types. These are similar to
* the snake_case report types from the backend, but should not be considered
* to be equivalent.
*
* These aren't technically Vuex modules yet, but they do correspond to
* namespaces in the store state, as if they were modules.
*/
export const MODULE_CONTAINER_SCANNING = 'containerScanning';
export const MODULE_COVERAGE_FUZZING = 'coverageFuzzing';
export const MODULE_DAST = 'dast';
export const MODULE_DEPENDENCY_SCANNING = 'dependencyScanning';
/**
* Tracks snowplow event when user views report details
......
import { s__, sprintf } from '~/locale';
import { countVulnerabilities, groupedTextBuilder, statusIcon, groupedReportText } from './utils';
import { LOADING, ERROR, SUCCESS } from './constants';
import { statusIcon, groupedReportText } from './utils';
import messages from './messages';
export {
allReportsHaveError,
anyReportHasError,
anyReportHasIssues,
areAllReportsLoading,
areReportsLoading,
groupedSummaryText,
summaryCounts,
summaryStatus,
} from '~/vue_shared/security_reports/store/getters';
export const groupedContainerScanningText = ({ containerScanning }) =>
groupedReportText(
containerScanning,
......@@ -30,65 +39,6 @@ export const groupedCoverageFuzzingText = ({ coverageFuzzing }) =>
messages.COVERAGE_FUZZING_IS_LOADING,
);
export const summaryCounts = ({
containerScanning,
dast,
dependencyScanning,
sast,
secretDetection,
coverageFuzzing,
} = {}) => {
const allNewVulns = [
...containerScanning.newIssues,
...dast.newIssues,
...dependencyScanning.newIssues,
...sast.newIssues,
...secretDetection.newIssues,
...coverageFuzzing.newIssues,
];
return countVulnerabilities(allNewVulns);
};
export const groupedSummaryText = (state, getters) => {
const reportType = s__('ciReport|Security scanning');
let status = '';
// All reports are loading
if (getters.areAllReportsLoading) {
return { message: sprintf(messages.TRANSLATION_IS_LOADING, { reportType }) };
}
// All reports returned error
if (getters.allReportsHaveError) {
return { message: s__('ciReport|Security scanning failed loading any results') };
}
if (getters.areReportsLoading && getters.anyReportHasError) {
status = s__('ciReport|is loading, errors when loading results');
} else if (getters.areReportsLoading && !getters.anyReportHasError) {
status = s__('ciReport|is loading');
} else if (!getters.areReportsLoading && getters.anyReportHasError) {
status = s__('ciReport|: Loading resulted in an error');
}
const { critical, high, other } = getters.summaryCounts;
return groupedTextBuilder({ reportType, status, critical, high, other });
};
export const summaryStatus = (state, getters) => {
if (getters.areReportsLoading) {
return LOADING;
}
if (getters.anyReportHasError || getters.anyReportHasIssues) {
return ERROR;
}
return SUCCESS;
};
export const containerScanningStatusIcon = ({ containerScanning }) =>
statusIcon(
containerScanning.isLoading,
......@@ -109,61 +59,8 @@ export const dependencyScanningStatusIcon = ({ dependencyScanning }) =>
export const coverageFuzzingStatusIcon = ({ coverageFuzzing }) =>
statusIcon(coverageFuzzing.isLoading, coverageFuzzing.hasError, coverageFuzzing.newIssues.length);
export const areReportsLoading = state =>
state.sast.isLoading ||
state.dast.isLoading ||
state.containerScanning.isLoading ||
state.dependencyScanning.isLoading ||
state.secretDetection.isLoading ||
state.coverageFuzzing.isLoading;
export const areAllReportsLoading = state =>
state.sast.isLoading &&
state.dast.isLoading &&
state.containerScanning.isLoading &&
state.dependencyScanning.isLoading &&
state.secretDetection.isLoading &&
state.coverageFuzzing.isLoading;
export const allReportsHaveError = state =>
state.sast.hasError &&
state.dast.hasError &&
state.containerScanning.hasError &&
state.dependencyScanning.hasError &&
state.secretDetection.hasError &&
state.coverageFuzzing.hasError;
export const anyReportHasError = state =>
state.sast.hasError ||
state.dast.hasError ||
state.containerScanning.hasError ||
state.dependencyScanning.hasError ||
state.secretDetection.hasError ||
state.coverageFuzzing.hasError;
export const noBaseInAllReports = state =>
!state.sast.hasBaseReport &&
!state.dast.hasBaseReport &&
!state.containerScanning.hasBaseReport &&
!state.dependencyScanning.hasBaseReport &&
!state.secretDetection.hasBaseReport &&
!state.coverageFuzzing.hasBaseReport;
export const anyReportHasIssues = state =>
state.sast.newIssues.length > 0 ||
state.dast.newIssues.length > 0 ||
state.containerScanning.newIssues.length > 0 ||
state.dependencyScanning.newIssues.length > 0 ||
state.secretDetection.newIssues.length > 0 ||
state.coverageFuzzing.newIssues.length > 0;
export const isBaseSecurityReportOutOfDate = state =>
state.sast.baseReportOutofDate ||
state.dast.baseReportOutofDate ||
state.containerScanning.baseReportOutofDate ||
state.dependencyScanning.baseReportOutofDate ||
state.secretDetection.baseReportOutofDate ||
state.coverageFuzzing.baseReportOutofDate;
state.reportTypes.some(reportType => state[reportType].baseReportOutofDate);
export const canCreateIssue = state => Boolean(state.createVulnerabilityFeedbackIssuePath);
......
......@@ -6,6 +6,7 @@ import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations';
import state from './state';
import { MODULE_SAST, MODULE_SECRET_DETECTION } from './constants';
import sast from './modules/sast';
import secretDetection from './modules/secret_detection';
......@@ -15,8 +16,8 @@ Vue.use(Vuex);
export default () =>
new Vuex.Store({
modules: {
sast,
secretDetection,
[MODULE_SAST]: sast,
[MODULE_SECRET_DETECTION]: secretDetection,
pipelineJobs,
},
actions,
......
import * as types from './mutation_types';
import { MODULE_SAST, MODULE_SECRET_DETECTION } from './constants';
export const updateIssueActionsMap = {
sast: 'sast/updateVulnerability',
sast: `${MODULE_SAST}/updateVulnerability`,
dependency_scanning: 'updateDependencyScanningIssue',
container_scanning: 'updateContainerScanningIssue',
dast: 'updateDastIssue',
secret_detection: 'secretDetection/updateVulnerability',
secret_detection: `${MODULE_SECRET_DETECTION}/updateVulnerability`,
coverage_fuzzing: 'updateCoverageFuzzingIssue',
};
......
import { s__, sprintf } from '~/locale';
const TRANSLATION_IS_LOADING = s__('ciReport|%{reportType} is loading');
const TRANSLATION_HAS_ERROR = s__('ciReport|%{reportType}: Loading resulted in an error');
import {
TRANSLATION_IS_LOADING,
TRANSLATION_HAS_ERROR,
} from '~/vue_shared/security_reports/store/messages';
const SAST = s__('ciReport|SAST');
const DAST = s__('ciReport|DAST');
......
import {
MODULE_CONTAINER_SCANNING,
MODULE_COVERAGE_FUZZING,
MODULE_DAST,
MODULE_DEPENDENCY_SCANNING,
MODULE_SAST,
MODULE_SECRET_DETECTION,
} from './constants';
export default () => ({
blobPath: {
head: null,
......@@ -13,7 +22,16 @@ export default () => ({
createVulnerabilityFeedbackDismissalPath: null,
pipelineId: null,
containerScanning: {
reportTypes: [
MODULE_CONTAINER_SCANNING,
MODULE_COVERAGE_FUZZING,
MODULE_DAST,
MODULE_DEPENDENCY_SCANNING,
MODULE_SAST,
MODULE_SECRET_DETECTION,
],
[MODULE_CONTAINER_SCANNING]: {
paths: {
head: null,
base: null,
......@@ -28,7 +46,7 @@ export default () => ({
baseReportOutofDate: false,
hasBaseReport: false,
},
dast: {
[MODULE_DAST]: {
paths: {
head: null,
base: null,
......@@ -44,7 +62,7 @@ export default () => ({
hasBaseReport: false,
scans: [],
},
coverageFuzzing: {
[MODULE_COVERAGE_FUZZING]: {
paths: {
head: null,
base: null,
......@@ -60,7 +78,7 @@ export default () => ({
baseReportOutofDate: false,
hasBaseReport: false,
},
dependencyScanning: {
[MODULE_DEPENDENCY_SCANNING]: {
paths: {
head: null,
base: null,
......
import { CRITICAL, HIGH } from 'ee/security_dashboard/store/modules/vulnerabilities/constants';
import { __, n__, sprintf } from '~/locale';
import {
groupedTextBuilder,
countVulnerabilities,
} from '~/vue_shared/security_reports/store/utils';
export { groupedTextBuilder, countVulnerabilities };
/**
* Returns the index of an issue in given list
......@@ -9,59 +13,6 @@ import { __, n__, sprintf } from '~/locale';
export const findIssueIndex = (issues, issue) =>
issues.findIndex(el => el.project_fingerprint === issue.project_fingerprint);
const createCountMessage = ({ critical, high, other, total }) => {
const otherMessage = n__('%d Other', '%d Others', other);
const countMessage = __(
'%{criticalStart}%{critical} Critical%{criticalEnd} %{highStart}%{high} High%{highEnd} and %{otherStart}%{otherMessage}%{otherEnd}',
);
return total ? sprintf(countMessage, { critical, high, otherMessage }) : '';
};
const createStatusMessage = ({ reportType, status, total }) => {
const vulnMessage = n__('vulnerability', 'vulnerabilities', total);
let message;
if (status) {
message = __('%{reportType} %{status}');
} else if (!total) {
message = __('%{reportType} detected %{totalStart}no%{totalEnd} vulnerabilities.');
} else {
message = __(
'%{reportType} detected %{totalStart}%{total}%{totalEnd} potential %{vulnMessage}',
);
}
return sprintf(message, { reportType, status, total, vulnMessage });
};
/**
* Takes an object of options and returns the object with an externalized string representing
* the critical, high, and other severity vulnerabilities for a given report.
*
* The resulting string _may_ still contain sprintf-style placeholders. These
* are left in place so they can be replaced with markup, via the
* SecuritySummary component.
* @param {{reportType: string, status: string, critical: number, high: number, other: number}} options
* @returns {Object} the parameters with an externalized string
*/
export const groupedTextBuilder = ({
reportType = '',
status = '',
critical = 0,
high = 0,
other = 0,
} = {}) => {
const total = critical + high + other;
return {
countMessage: createCountMessage({ critical, high, other, total }),
message: createStatusMessage({ reportType, status, total }),
critical,
high,
other,
status,
total,
};
};
export const statusIcon = (loading = false, failed = false, newIssues = 0, neutralIssues = 0) => {
if (loading) {
return 'loading';
......@@ -74,25 +25,6 @@ export const statusIcon = (loading = false, failed = false, newIssues = 0, neutr
return 'success';
};
/**
* Counts vulnerabilities.
* Returns the amount of critical, high, and other vulnerabilities.
*
* @param {Array} vulnerabilities The raw vulnerabilities to parse
* @returns {{critical: number, high: number, other: number}}
*/
export const countVulnerabilities = (vulnerabilities = []) => {
const critical = vulnerabilities.filter(vuln => vuln.severity === CRITICAL).length;
const high = vulnerabilities.filter(vuln => vuln.severity === HIGH).length;
const other = vulnerabilities.length - critical - high;
return {
critical,
high,
other,
};
};
/**
* Generates a report message based on some of the report parameters and supplied messages.
*
......
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import LicenseManagement from 'ee/vue_shared/license_compliance/mr_widget_license_report.vue';
import { LOADING, ERROR, SUCCESS } from 'ee/vue_shared/security_reports/store/constants';
import { TEST_HOST } from 'spec/test_constants';
import ReportItem from '~/reports/components/report_item.vue';
import ReportSection from '~/reports/components/report_section.vue';
import { LOADING, ERROR, SUCCESS } from '~/reports/constants';
import {
approvedLicense,
blacklistedLicense,
......
......@@ -11,12 +11,13 @@ import {
groupedCoverageFuzzingText,
groupedSummaryText,
allReportsHaveError,
noBaseInAllReports,
areReportsLoading,
areAllReportsLoading,
containerScanningStatusIcon,
dastStatusIcon,
dependencyScanningStatusIcon,
anyReportHasError,
anyReportHasIssues,
summaryCounts,
isBaseSecurityReportOutOfDate,
canCreateIssue,
......@@ -214,6 +215,29 @@ describe('Security reports getters', () => {
});
});
describe('areAllReportsLoading', () => {
it('returns true when all reports are loading', () => {
state.sast.isLoading = true;
state.dast.isLoading = true;
state.containerScanning.isLoading = true;
state.dependencyScanning.isLoading = true;
state.secretDetection.isLoading = true;
state.coverageFuzzing.isLoading = true;
expect(areAllReportsLoading(state)).toEqual(true);
});
it('returns false when some of the reports are loading', () => {
state.sast.isLoading = true;
expect(areAllReportsLoading(state)).toEqual(false);
});
it('returns false when none of the reports are loading', () => {
expect(areAllReportsLoading(state)).toEqual(false);
});
});
describe('allReportsHaveError', () => {
it('returns true when all reports have error', () => {
state.sast.hasError = true;
......@@ -252,15 +276,15 @@ describe('Security reports getters', () => {
});
});
describe('noBaseInAllReports', () => {
it('returns true when none reports have base', () => {
expect(noBaseInAllReports(state)).toEqual(true);
});
describe('anyReportHasIssues', () => {
it('returns true when any of the reports has new issues', () => {
state.dast.newIssues.push(generateVuln(LOW));
it('returns false when any of the reports has a base', () => {
state.dast.hasBaseReport = true;
expect(anyReportHasIssues(state)).toEqual(true);
});
expect(noBaseInAllReports(state)).toEqual(false);
it('returns false when none of the reports has error', () => {
expect(anyReportHasIssues(state)).toEqual(false);
});
});
......
import createState from '~/vue_shared/security_reports/store/state';
import createSastState from '~/vue_shared/security_reports/store/modules/sast/state';
import createSecretScanningState from '~/vue_shared/security_reports/store/modules/secret_detection/state';
import { groupedTextBuilder } from '~/vue_shared/security_reports/store/utils';
import {
groupedSummaryText,
allReportsHaveError,
areReportsLoading,
anyReportHasError,
areAllReportsLoading,
anyReportHasIssues,
summaryCounts,
} from '~/vue_shared/security_reports/store/getters';
import { CRITICAL, HIGH, LOW } from '~/vulnerabilities/constants';
const generateVuln = severity => ({ severity });
describe('Security reports getters', () => {
let state;
beforeEach(() => {
state = createState();
state.sast = createSastState();
state.secretDetection = createSecretScanningState();
});
describe('summaryCounts', () => {
it('returns 0 count for empty state', () => {
expect(summaryCounts(state)).toEqual({
critical: 0,
high: 0,
other: 0,
});
});
describe('combines all reports', () => {
it('of the same severity', () => {
state.sast.newIssues = [generateVuln(CRITICAL)];
state.secretDetection.newIssues = [generateVuln(CRITICAL)];
expect(summaryCounts(state)).toEqual({
critical: 2,
high: 0,
other: 0,
});
});
it('of different severities', () => {
state.sast.newIssues = [generateVuln(CRITICAL)];
state.secretDetection.newIssues = [generateVuln(HIGH), generateVuln(LOW)];
expect(summaryCounts(state)).toEqual({
critical: 1,
high: 1,
other: 1,
});
});
});
});
describe('groupedSummaryText', () => {
it('returns failed text', () => {
expect(
groupedSummaryText(state, {
allReportsHaveError: true,
areReportsLoading: false,
summaryCounts: {},
}),
).toEqual({ message: 'Security scanning failed loading any results' });
});
it('returns `is loading` as status text', () => {
expect(
groupedSummaryText(state, {
allReportsHaveError: false,
areReportsLoading: true,
summaryCounts: {},
}),
).toEqual(
groupedTextBuilder({
reportType: 'Security scanning',
critical: 0,
high: 0,
other: 0,
status: 'is loading',
}),
);
});
it('returns no new status text if there are existing ones', () => {
expect(
groupedSummaryText(state, {
allReportsHaveError: false,
areReportsLoading: false,
summaryCounts: {},
}),
).toEqual(
groupedTextBuilder({
reportType: 'Security scanning',
critical: 0,
high: 0,
other: 0,
status: '',
}),
);
});
});
describe('areReportsLoading', () => {
it('returns true when any report is loading', () => {
state.sast.isLoading = true;
expect(areReportsLoading(state)).toEqual(true);
});
it('returns false when none of the reports are loading', () => {
expect(areReportsLoading(state)).toEqual(false);
});
});
describe('areAllReportsLoading', () => {
it('returns true when all reports are loading', () => {
state.sast.isLoading = true;
state.secretDetection.isLoading = true;
expect(areAllReportsLoading(state)).toEqual(true);
});
it('returns false when some of the reports are loading', () => {
state.sast.isLoading = true;
expect(areAllReportsLoading(state)).toEqual(false);
});
it('returns false when none of the reports are loading', () => {
expect(areAllReportsLoading(state)).toEqual(false);
});
});
describe('allReportsHaveError', () => {
it('returns true when all reports have error', () => {
state.sast.hasError = true;
state.secretDetection.hasError = true;
expect(allReportsHaveError(state)).toEqual(true);
});
it('returns false when none of the reports have error', () => {
expect(allReportsHaveError(state)).toEqual(false);
});
it('returns false when one of the reports does not have error', () => {
state.secretDetection.hasError = true;
expect(allReportsHaveError(state)).toEqual(false);
});
});
describe('anyReportHasError', () => {
it('returns true when any of the reports has error', () => {
state.sast.hasError = true;
expect(anyReportHasError(state)).toEqual(true);
});
it('returns false when none of the reports has error', () => {
expect(anyReportHasError(state)).toEqual(false);
});
});
describe('anyReportHasIssues', () => {
it('returns true when any of the reports has new issues', () => {
state.sast.newIssues.push(generateVuln(LOW));
expect(anyReportHasIssues(state)).toEqual(true);
});
it('returns false when none of the reports has error', () => {
expect(anyReportHasIssues(state)).toEqual(false);
});
});
});
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