Commit 8eab7c9c authored by Mark Florian's avatar Mark Florian

Extract and move Secret Detection module to CE

This extracts and moves most of a Secret Detection Vuex module from EE
to CE as part of the effort to [add vulnerability counts to the CE MR
security widget][issue].

It's not a straight-forward move, though. The Secret Detection module
contains actions, mutations and getters specific to EE functionality, so
the approach taken here was to move all _common_ behaviour to CE
version, and make the EE version _extend_ the CE version.

A similar approach was taken for the [SAST module][sast_mr].

[issue]: https://gitlab.com/gitlab-org/gitlab/-/issues/273423
[sast_mr]: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47067
parent 4a03d54e
import { fetchDiffData } from '../../utils';
import * as types from './mutation_types';
export const setSecretScanningDiffEndpoint = ({ commit }, path) =>
commit(types.SET_SECRET_SCANNING_DIFF_ENDPOINT, path);
export const requestSecretScanningDiff = ({ commit }) => commit(types.REQUEST_SECRET_SCANNING_DIFF);
export const receiveSecretScanningDiffSuccess = ({ commit }, response) =>
commit(types.RECEIVE_SECRET_SCANNING_DIFF_SUCCESS, response);
export const receiveSecretScanningDiffError = ({ commit }, error) =>
commit(types.RECEIVE_SECRET_SCANNING_DIFF_ERROR, error);
export const fetchSecretScanningDiff = ({ state, rootState, dispatch }) => {
dispatch('requestSecretScanningDiff');
return fetchDiffData(rootState, state.paths.diffEndpoint, 'secret_detection')
.then(data => {
dispatch('receiveSecretScanningDiffSuccess', data);
})
.catch(() => {
dispatch('receiveSecretScanningDiffError');
});
};
import state from './state';
import mutations from './mutations';
import * as actions from './actions';
export default {
namespaced: true,
state,
mutations,
actions,
};
export const RECEIVE_SECRET_SCANNING_DIFF_SUCCESS = 'RECEIVE_SECRET_SCANNING_DIFF_SUCCESS';
export const RECEIVE_SECRET_SCANNING_DIFF_ERROR = 'RECEIVE_SECRET_SCANNING_DIFF_ERROR';
export const REQUEST_SECRET_SCANNING_DIFF = 'REQUEST_SECRET_SCANNING_DIFF';
export const SET_SECRET_SCANNING_DIFF_ENDPOINT = 'SET_SECRET_SCANNING_DIFF_ENDPOINT';
import Vue from 'vue';
import { parseDiff } from '~/vue_shared/security_reports/store/utils';
import * as types from './mutation_types';
export default {
[types.SET_SECRET_SCANNING_DIFF_ENDPOINT](state, path) {
Vue.set(state.paths, 'diffEndpoint', path);
},
[types.REQUEST_SECRET_SCANNING_DIFF](state) {
Vue.set(state, 'isLoading', true);
},
[types.RECEIVE_SECRET_SCANNING_DIFF_SUCCESS](state, { diff, enrichData }) {
const { added, fixed, existing } = parseDiff(diff, enrichData);
const baseReportOutofDate = diff.base_report_out_of_date || false;
const hasBaseReport = Boolean(diff.base_report_created_at);
Vue.set(state, 'isLoading', false);
Vue.set(state, 'newIssues', added);
Vue.set(state, 'resolvedIssues', fixed);
Vue.set(state, 'allIssues', existing);
Vue.set(state, 'baseReportOutofDate', baseReportOutofDate);
Vue.set(state, 'hasBaseReport', hasBaseReport);
},
[types.RECEIVE_SECRET_SCANNING_DIFF_ERROR](state) {
Vue.set(state, 'isLoading', false);
Vue.set(state, 'hasError', true);
},
};
export default () => ({
paths: {
head: null,
base: null,
diffEndpoint: null,
},
isLoading: false,
hasError: false,
newIssues: [],
resolvedIssues: [],
allIssues: [],
baseReportOutofDate: false,
hasBaseReport: false,
});
......@@ -204,12 +204,10 @@ export default {
'groupedContainerScanningText',
'groupedDastText',
'groupedDependencyText',
'groupedSecretScanningText',
'groupedCoverageFuzzingText',
'containerScanningStatusIcon',
'dastStatusIcon',
'dependencyScanningStatusIcon',
'secretScanningStatusIcon',
'coverageFuzzingStatusIcon',
'isBaseSecurityReportOutOfDate',
'canCreateIssue',
......@@ -217,6 +215,7 @@ export default {
'canDismissVulnerability',
]),
...mapGetters('sast', ['groupedSastText', 'sastStatusIcon']),
...mapGetters('secretScanning', ['groupedSecretScanningText', 'secretScanningStatusIcon']),
...mapGetters('pipelineJobs', ['hasFuzzingArtifacts', 'fuzzingJobsWithArtifact']),
securityTab() {
return `${this.pipelinePath}/security`;
......@@ -237,7 +236,7 @@ export default {
hasSastReports() {
return this.enabledReports.sast;
},
hasSecretScanningReports() {
hasSecretDetectionReports() {
return this.enabledReports.secretDetection;
},
isMRActive() {
......@@ -352,8 +351,6 @@ export default {
'setDependencyScanningDiffEndpoint',
'fetchDastDiff',
'setDastDiffEndpoint',
'fetchSecretScanningDiff',
'setSecretScanningDiffEndpoint',
'fetchCoverageFuzzingDiff',
'setCoverageFuzzingDiffEndpoint',
]),
......@@ -361,6 +358,10 @@ export default {
setSastDiffEndpoint: 'setDiffEndpoint',
fetchSastDiff: 'fetchDiff',
}),
...mapActions('secretScanning', {
setSecretScanningDiffEndpoint: 'setSecretScanningDiffEndpoint',
fetchSecretScanningDiff: 'fetchSecretScanningDiff',
}),
...mapActions('pipelineJobs', ['fetchPipelineJobs', 'setPipelineJobsPath', 'setProjectId']),
...mapActions('pipelineJobs', {
setPipelineJobsId: 'setPipelineId',
......
......@@ -173,36 +173,6 @@ export const fetchCoverageFuzzingDiff = ({ state, dispatch }) => {
export const updateCoverageFuzzingIssue = ({ commit }, issue) =>
commit(types.UPDATE_COVERAGE_FUZZING_ISSUE, issue);
/**
* SECRET SCANNING
*/
export const setSecretScanningDiffEndpoint = ({ commit }, path) =>
commit(types.SET_SECRET_SCANNING_DIFF_ENDPOINT, path);
export const requestSecretScanningDiff = ({ commit }) => commit(types.REQUEST_SECRET_SCANNING_DIFF);
export const receiveSecretScanningDiffSuccess = ({ commit }, response) =>
commit(types.RECEIVE_SECRET_SCANNING_DIFF_SUCCESS, response);
export const receiveSecretScanningDiffError = ({ commit }) =>
commit(types.RECEIVE_SECRET_SCANNING_DIFF_ERROR);
export const fetchSecretScanningDiff = ({ state, dispatch }) => {
dispatch('requestSecretScanningDiff');
return fetchDiffData(state, state.secretScanning.paths.diffEndpoint, 'secret_detection')
.then(data => {
dispatch('receiveSecretScanningDiffSuccess', data);
})
.catch(() => {
dispatch('receiveSecretScanningDiffError');
});
};
export const updateSecretScanningIssue = ({ commit }, issue) =>
commit(types.UPDATE_SECRET_SCANNING_ISSUE, issue);
export const openModal = ({ dispatch }, payload) => {
dispatch('setModalData', payload);
......
......@@ -11,14 +11,6 @@ export const groupedContainerScanningText = ({ containerScanning }) =>
messages.CONTAINER_SCANNING_IS_LOADING,
);
export const groupedSecretScanningText = ({ secretScanning }) =>
groupedReportText(
secretScanning,
messages.SECRET_SCANNING,
messages.SECRET_SCANNING_HAS_ERROR,
messages.SECRET_SCANNING_IS_LOADING,
);
export const groupedDastText = ({ dast }) =>
groupedReportText(dast, messages.DAST, messages.DAST_HAS_ERROR, messages.DAST_IS_LOADING);
......@@ -114,9 +106,6 @@ export const dependencyScanningStatusIcon = ({ dependencyScanning }) =>
dependencyScanning.newIssues.length,
);
export const secretScanningStatusIcon = ({ secretScanning }) =>
statusIcon(secretScanning.isLoading, secretScanning.hasError, secretScanning.newIssues.length);
export const coverageFuzzingStatusIcon = ({ coverageFuzzing }) =>
statusIcon(coverageFuzzing.isLoading, coverageFuzzing.hasError, coverageFuzzing.newIssues.length);
......
......@@ -8,6 +8,7 @@ import mutations from './mutations';
import state from './state';
import sast from './modules/sast';
import secretScanning from './modules/secret_detection';
Vue.use(Vuex);
......@@ -15,6 +16,7 @@ export default () =>
new Vuex.Store({
modules: {
sast,
secretScanning,
pipelineJobs,
},
actions,
......
import * as types from './mutation_types';
const updateIssueActionsMap = {
export const updateIssueActionsMap = {
sast: 'sast/updateVulnerability',
dependency_scanning: 'updateDependencyScanningIssue',
container_scanning: 'updateContainerScanningIssue',
dast: 'updateDastIssue',
secret_scanning: 'updateSecretScanningIssue',
secret_scanning: 'secretScanning/updateSecretScanningIssue',
coverage_fuzzing: 'updateCoverageFuzzingIssue',
};
......
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';
import messages from '../../messages';
export const groupedSastText = state =>
groupedReportText(state, SAST, SAST_HAS_ERROR, SAST_IS_LOADING);
groupedReportText(state, messages.SAST, messages.SAST_HAS_ERROR, messages.SAST_IS_LOADING);
export const sastStatusIcon = ({ isLoading, hasError, newIssues }) =>
statusIcon(isLoading, hasError, newIssues.length);
import * as types from './mutation_types';
export * from '~/vue_shared/security_reports/store/modules/secret_detection/actions';
export const updateSecretScanningIssue = ({ commit }, issue) =>
commit(types.UPDATE_SECRET_SCANNING_ISSUE, issue);
import { statusIcon, groupedReportText } from '../../utils';
import messages from '../../messages';
export const groupedSecretScanningText = state =>
groupedReportText(
state,
messages.SECRET_SCANNING,
messages.SECRET_SCANNING_HAS_ERROR,
messages.SECRET_SCANNING_IS_LOADING,
);
export const secretScanningStatusIcon = ({ isLoading, hasError, newIssues }) =>
statusIcon(isLoading, hasError, newIssues.length);
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 * from '~/vue_shared/security_reports/store/modules/secret_detection/mutation_types';
export const UPDATE_SECRET_SCANNING_ISSUE = 'UPDATE_SECRET_SCANNING_ISSUE';
import ceMutations from '~/vue_shared/security_reports/store/modules/secret_detection/mutations';
import { findIssueIndex } from '../../utils';
import * as types from './mutation_types';
export default {
...ceMutations,
[types.UPDATE_SECRET_SCANNING_ISSUE](state, issue) {
// Find issue in the correct list and update it
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);
}
const allIssuesIndex = findIssueIndex(state.allIssues, issue);
if (allIssuesIndex !== -1) {
state.allIssues.splice(allIssuesIndex, 1, issue);
}
},
};
export { default } from '~/vue_shared/security_reports/store/modules/secret_detection/state';
......@@ -30,12 +30,6 @@ export const REQUEST_DEPENDENCY_SCANNING_DIFF = 'REQUEST_DEPENDENCY_SCANNING_DIF
export const RECEIVE_DEPENDENCY_SCANNING_DIFF_SUCCESS = 'RECEIVE_DEPENDENCY_SCANNING_DIFF_SUCCESS';
export const RECEIVE_DEPENDENCY_SCANNING_DIFF_ERROR = 'RECEIVE_DEPENDENCY_SCANNING_DIFF_ERROR';
// SECRET SCANNING
export const SET_SECRET_SCANNING_DIFF_ENDPOINT = 'SET_SECRET_SCANNING_DIFF_ENDPOINT';
export const REQUEST_SECRET_SCANNING_DIFF = 'REQUEST_SECRET_SCANNING_DIFF';
export const RECEIVE_SECRET_SCANNING_DIFF_SUCCESS = 'RECEIVE_SECRET_SCANNING_DIFF_SUCCESS';
export const RECEIVE_SECRET_SCANNING_DIFF_ERROR = 'RECEIVE_SECRET_SCANNING_DIFF_ERROR';
// COVERAGE FUZZING
export const SET_COVERAGE_FUZZING_DIFF_ENDPOINT = 'SET_COVERAGE_FUZZING_DIFF_ENDPOINT';
export const REQUEST_COVERAGE_FUZZING_DIFF = 'REQUEST_COVERAGE_FUZZING_DIFF';
......@@ -69,7 +63,6 @@ export const HIDE_DISMISSAL_DELETE_BUTTONS = 'HIDE_DISMISSAL_DELETE_BUTTONS';
export const UPDATE_DEPENDENCY_SCANNING_ISSUE = 'UPDATE_DEPENDENCY_SCANNING_ISSUE';
export const UPDATE_CONTAINER_SCANNING_ISSUE = 'UPDATE_CONTAINER_SCANNING_ISSUE';
export const UPDATE_DAST_ISSUE = 'UPDATE_DAST_ISSUE';
export const UPDATE_SECRET_SCANNING_ISSUE = 'UPDATE_SECRET_SCANNING_ISSUE';
export const UPDATE_COVERAGE_FUZZING_ISSUE = 'UPDATE_COVERAGE_FUZZING_ISSUE';
export const OPEN_DISMISSAL_COMMENT_BOX = 'OPEN_DISMISSAL_COMMENT_BOX ';
......
......@@ -160,33 +160,6 @@ export default {
Vue.set(state.dependencyScanning, 'hasError', true);
},
// SECRET SCANNING
[types.SET_SECRET_SCANNING_DIFF_ENDPOINT](state, path) {
Vue.set(state.secretScanning.paths, 'diffEndpoint', path);
},
[types.REQUEST_SECRET_SCANNING_DIFF](state) {
Vue.set(state.secretScanning, 'isLoading', true);
},
[types.RECEIVE_SECRET_SCANNING_DIFF_SUCCESS](state, { diff, enrichData }) {
const { added, fixed, existing } = parseDiff(diff, enrichData);
const baseReportOutofDate = diff.base_report_out_of_date || false;
const hasBaseReport = Boolean(diff.base_report_created_at);
Vue.set(state.secretScanning, 'isLoading', false);
Vue.set(state.secretScanning, 'newIssues', added);
Vue.set(state.secretScanning, 'resolvedIssues', fixed);
Vue.set(state.secretScanning, 'allIssues', existing);
Vue.set(state.secretScanning, 'baseReportOutofDate', baseReportOutofDate);
Vue.set(state.secretScanning, 'hasBaseReport', hasBaseReport);
},
[types.RECEIVE_SECRET_SCANNING_DIFF_ERROR](state) {
Vue.set(state.secretScanning, 'isLoading', false);
Vue.set(state.secretScanning, 'hasError', true);
},
[types.SET_ISSUE_MODAL_DATA](state, payload) {
const { issue, status } = payload;
......@@ -298,26 +271,6 @@ export default {
}
},
[types.UPDATE_SECRET_SCANNING_ISSUE](state, issue) {
// Find issue in the correct list and update it
const newIssuesIndex = findIssueIndex(state.secretScanning.newIssues, issue);
if (newIssuesIndex !== -1) {
state.secretScanning.newIssues.splice(newIssuesIndex, 1, issue);
return;
}
const resolvedIssuesIndex = findIssueIndex(state.secretScanning.resolvedIssues, issue);
if (resolvedIssuesIndex !== -1) {
state.secretScanning.resolvedIssues.splice(resolvedIssuesIndex, 1, issue);
}
const allIssuesIndex = findIssueIndex(state.secretScanning.allIssues, issue);
if (allIssuesIndex !== -1) {
state.secretScanning.allIssues.splice(allIssuesIndex, 1, issue);
}
},
[types.REQUEST_CREATE_ISSUE](state) {
state.isCreatingIssue = true;
// reset error in case previous state was error
......
......@@ -76,22 +76,6 @@ export default () => ({
baseReportOutofDate: false,
hasBaseReport: false,
},
secretScanning: {
paths: {
head: null,
base: null,
diffEndpoint: null,
},
isLoading: false,
hasError: false,
newIssues: [],
resolvedIssues: [],
allIssues: [],
baseReportOutofDate: false,
hasBaseReport: false,
},
modal: {
title: null,
......
......@@ -4,6 +4,7 @@ import GroupedSecurityReportsApp from 'ee/vue_shared/security_reports/grouped_se
import appStore from 'ee/vue_shared/security_reports/store';
import * as types from 'ee/vue_shared/security_reports/store/mutation_types';
import * as sastTypes from 'ee/vue_shared/security_reports/store/modules/sast/mutation_types';
import * as secretScanningTypes from 'ee/vue_shared/security_reports/store/modules/secret_detection/mutation_types';
import { mount } from '@vue/test-utils';
import { waitForMutation } from 'helpers/vue_test_utils_helper';
import { trimText } from 'helpers/text_helper';
......@@ -137,7 +138,10 @@ describe('Grouped security reports app', () => {
waitForMutation(wrapper.vm.$store, types.RECEIVE_CONTAINER_SCANNING_DIFF_ERROR),
waitForMutation(wrapper.vm.$store, types.RECEIVE_DAST_DIFF_ERROR),
waitForMutation(wrapper.vm.$store, types.RECEIVE_DEPENDENCY_SCANNING_DIFF_ERROR),
waitForMutation(wrapper.vm.$store, types.RECEIVE_SECRET_SCANNING_DIFF_ERROR),
waitForMutation(
wrapper.vm.$store,
`secretScanning/${secretScanningTypes.RECEIVE_SECRET_SCANNING_DIFF_ERROR}`,
),
waitForMutation(wrapper.vm.$store, types.RECEIVE_COVERAGE_FUZZING_DIFF_ERROR),
]);
});
......@@ -222,7 +226,10 @@ describe('Grouped security reports app', () => {
waitForMutation(wrapper.vm.$store, types.RECEIVE_DAST_DIFF_SUCCESS),
waitForMutation(wrapper.vm.$store, types.RECEIVE_CONTAINER_SCANNING_DIFF_SUCCESS),
waitForMutation(wrapper.vm.$store, types.RECEIVE_DEPENDENCY_SCANNING_DIFF_SUCCESS),
waitForMutation(wrapper.vm.$store, types.RECEIVE_SECRET_SCANNING_DIFF_SUCCESS),
waitForMutation(
wrapper.vm.$store,
`secretScanning/${secretScanningTypes.RECEIVE_SECRET_SCANNING_DIFF_SUCCESS}`,
),
waitForMutation(wrapper.vm.$store, types.RECEIVE_COVERAGE_FUZZING_DIFF_SUCCESS),
]);
});
......@@ -272,7 +279,10 @@ describe('Grouped security reports app', () => {
waitForMutation(wrapper.vm.$store, types.RECEIVE_DAST_DIFF_SUCCESS),
waitForMutation(wrapper.vm.$store, types.RECEIVE_CONTAINER_SCANNING_DIFF_SUCCESS),
waitForMutation(wrapper.vm.$store, types.RECEIVE_DEPENDENCY_SCANNING_DIFF_SUCCESS),
waitForMutation(wrapper.vm.$store, types.RECEIVE_SECRET_SCANNING_DIFF_SUCCESS),
waitForMutation(
wrapper.vm.$store,
`secretScanning/${secretScanningTypes.RECEIVE_SECRET_SCANNING_DIFF_SUCCESS}`,
),
waitForMutation(wrapper.vm.$store, types.RECEIVE_COVERAGE_FUZZING_DIFF_SUCCESS),
]);
});
......@@ -564,7 +574,10 @@ describe('Grouped security reports app', () => {
},
});
return waitForMutation(wrapper.vm.$store, types.RECEIVE_SECRET_SCANNING_DIFF_SUCCESS);
return waitForMutation(
wrapper.vm.$store,
`secretScanning/${secretScanningTypes.RECEIVE_SECRET_SCANNING_DIFF_SUCCESS}`,
);
};
describe('enabled', () => {
......
......@@ -29,7 +29,6 @@ import {
updateDependencyScanningIssue,
updateContainerScanningIssue,
updateDastIssue,
updateSecretScanningIssue,
updateCoverageFuzzingIssue,
addDismissalComment,
receiveAddDismissalCommentError,
......@@ -53,10 +52,6 @@ import {
receiveDastDiffSuccess,
receiveDastDiffError,
fetchDastDiff,
setSecretScanningDiffEndpoint,
receiveSecretScanningDiffSuccess,
receiveSecretScanningDiffError,
fetchSecretScanningDiff,
setCoverageFuzzingDiffEndpoint,
receiveCoverageFuzzingDiffSuccess,
receiveCoverageFuzzingDiffError,
......@@ -70,7 +65,6 @@ import {
dastFeedbacks,
containerScanningFeedbacks,
dependencyScanningFeedbacks,
secretScanningFeedbacks,
coverageFuzzingFeedbacks,
} from '../mock_data';
import toasted from '~/vue_shared/plugins/global_toast';
......@@ -1087,26 +1081,6 @@ describe('security reports actions', () => {
});
});
describe('updateSecretScanningIssue', () => {
it('commits update secret scanning issue', done => {
const payload = { foo: 'bar' };
testAction(
updateSecretScanningIssue,
payload,
mockedState,
[
{
type: types.UPDATE_SECRET_SCANNING_ISSUE,
payload,
},
],
[],
done,
);
});
});
describe('updateDastIssue', () => {
it('commits update dast issue', done => {
const payload = { foo: 'bar' };
......@@ -1709,194 +1683,6 @@ describe('security reports actions', () => {
});
});
describe('setSecretScanningDiffEndpoint', () => {
it('should pass down the endpoint to the mutation', done => {
const payload = '/secret_scanning_endpoint.json';
testAction(
setSecretScanningDiffEndpoint,
payload,
mockedState,
[
{
type: types.SET_SECRET_SCANNING_DIFF_ENDPOINT,
payload,
},
],
[],
done,
);
});
});
describe('receiveSecretScanningDiffSuccess', () => {
it('should pass down the response to the mutation', done => {
const payload = { data: 'Effort yields its own rewards.' };
testAction(
receiveSecretScanningDiffSuccess,
payload,
mockedState,
[
{
type: types.RECEIVE_SECRET_SCANNING_DIFF_SUCCESS,
payload,
},
],
[],
done,
);
});
});
describe('receiveSecretScanningDiffError', () => {
it('should commit secret diff error mutation', done => {
testAction(
receiveSecretScanningDiffError,
undefined,
mockedState,
[
{
type: types.RECEIVE_SECRET_SCANNING_DIFF_ERROR,
},
],
[],
done,
);
});
});
describe('fetchSecretScanningDiff', () => {
const diff = { vulnerabilities: [] };
const endpoint = 'secret_scanning_diff.json';
beforeEach(() => {
mockedState.vulnerabilityFeedbackPath = 'vulnerabilities_feedback';
mockedState.canReadVulnerabilityFeedback = true;
mockedState.secretScanning.paths.diffEndpoint = endpoint;
});
describe('on success', () => {
it('should dispatch `receiveSecretScanningDiffSuccess`', done => {
mock.onGet(endpoint).reply(200, diff);
mock
.onGet('vulnerabilities_feedback', {
params: {
category: 'secret_detection',
},
})
.reply(200, secretScanningFeedbacks);
testAction(
fetchSecretScanningDiff,
null,
mockedState,
[],
[
{
type: 'requestSecretScanningDiff',
},
{
type: 'receiveSecretScanningDiffSuccess',
payload: {
diff,
enrichData: secretScanningFeedbacks,
},
},
],
done,
);
});
});
describe('when diff endpoint responds successfully and fetching vulnerability feedback is not authorized', () => {
beforeEach(() => {
mockedState.canReadVulnerabilityFeedback = false;
mock.onGet(endpoint).reply(200, diff);
});
it('should dispatch `secret_scanning`', done => {
testAction(
fetchSecretScanningDiff,
null,
mockedState,
[],
[
{
type: 'requestSecretScanningDiff',
},
{
type: 'receiveSecretScanningDiffSuccess',
payload: {
diff,
enrichData: [],
},
},
],
done,
);
});
});
describe('when vulnerabilities path errors', () => {
it('should dispatch `receiveSecretScanningError`', done => {
mock.onGet(endpoint).reply(500);
mock
.onGet('vulnerabilities_feedback', {
params: {
category: 'secret_scanning',
},
})
.reply(200, secretScanningFeedbacks);
testAction(
fetchSecretScanningDiff,
null,
mockedState,
[],
[
{
type: 'requestSecretScanningDiff',
},
{
type: 'receiveSecretScanningDiffError',
},
],
done,
);
});
});
describe('when feedback path errors', () => {
it('should dispatch `receiveSecretScanningError`', done => {
mock.onGet(endpoint).reply(200, diff);
mock
.onGet('vulnerabilities_feedback', {
params: {
category: 'secret_scanning',
},
})
.reply(500);
testAction(
fetchSecretScanningDiff,
null,
mockedState,
[],
[
{
type: 'requestSecretScanningDiff',
},
{
type: 'receiveSecretScanningDiffError',
},
],
done,
);
});
});
});
describe('setCoverageFuzzingDiffEndpoint', () => {
it('should pass down the endpoint to the mutation', done => {
const payload = '/coverage_fuzzing_endpoint.json';
......
import createState from 'ee/vue_shared/security_reports/store/state';
import createSastState from 'ee/vue_shared/security_reports/store/modules/sast/state';
import createSecretScanningState from 'ee/vue_shared/security_reports/store/modules/secret_detection/state';
import { groupedTextBuilder } from 'ee/vue_shared/security_reports/store/utils';
import {
groupedContainerScanningText,
groupedDastText,
groupedDependencyText,
groupedSecretScanningText,
groupedCoverageFuzzingText,
groupedSummaryText,
allReportsHaveError,
......@@ -38,11 +38,11 @@ describe('Security reports getters', () => {
beforeEach(() => {
state = createState();
state.sast = createSastState();
state.secretScanning = createSecretScanningState();
});
describe.each`
name | scanner | getter
${'Secret scanning'} | ${'secretScanning'} | ${groupedSecretScanningText}
${'Dependency scanning'} | ${'dependencyScanning'} | ${groupedDependencyText}
${'Container scanning'} | ${'containerScanning'} | ${groupedContainerScanningText}
${'DAST'} | ${'dast'} | ${groupedDastText}
......
import * as types from 'ee/vue_shared/security_reports/store/mutation_types';
import configureMediator from 'ee/vue_shared/security_reports/store/mediator';
import configureMediator, {
updateIssueActionsMap,
} from 'ee/vue_shared/security_reports/store/mediator';
const mockedStore = {
dispatch: jest.fn(),
......@@ -17,18 +19,14 @@ describe('security reports mediator', () => {
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;
it.each(Object.entries(updateIssueActionsMap).map(entry => entry.reverse()))(
`should trigger %s on when a %s is updated`,
(action, category) => {
const payload = { category };
mockedStore.commit({ type, payload });
expect(mockedStore.dispatch).toHaveBeenCalledWith(action, payload);
});
},
);
});
});
import {
SAST_HAS_ERROR,
SAST_IS_LOADING,
} from 'ee/vue_shared/security_reports/store/modules/sast/constants';
import messages from 'ee/vue_shared/security_reports/store/messages';
import * as getters from 'ee/vue_shared/security_reports/store/modules/sast/getters';
const createReport = (config = {}) => ({
......@@ -15,14 +12,14 @@ describe('groupedSastText', () => {
const sast = createReport({ hasError: true });
const result = getters.groupedSastText(sast);
expect(result).toStrictEqual({ message: SAST_HAS_ERROR });
expect(result).toStrictEqual({ message: messages.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).toStrictEqual({ message: SAST_IS_LOADING });
expect(result).toStrictEqual({ message: messages.SAST_IS_LOADING });
});
it('should call groupedTextBuilder if everything is fine', () => {
......
import testAction from 'helpers/vuex_action_helper';
import createState from 'ee/vue_shared/security_reports/store/modules/secret_detection/state';
import * as types from 'ee/vue_shared/security_reports/store/modules/secret_detection/mutation_types';
import * as actions from 'ee/vue_shared/security_reports/store/modules/secret_detection/actions';
const issue = {};
let state;
// See also the corresponding CE specs in
// spec/frontend/vue_shared/security_reports/store/modules/secret_detection/actions_spec.js
describe('EE secret detection report actions', () => {
beforeEach(() => {
state = createState();
});
describe('updateSecretScanningIssue', () => {
it(`should commit ${types.UPDATE_SECRET_SCANNING_ISSUE} with the correct response`, done => {
testAction(
actions.updateSecretScanningIssue,
issue,
state,
[
{
type: types.UPDATE_SECRET_SCANNING_ISSUE,
payload: issue,
},
],
[],
done,
);
});
});
});
import messages from 'ee/vue_shared/security_reports/store/messages';
import * as getters from 'ee/vue_shared/security_reports/store/modules/secret_detection/getters';
const createReport = (config = {}) => ({
paths: [],
newIssues: [],
...config,
});
describe('groupedSecretScanningText', () => {
it("should return the error message if there's an error", () => {
const report = createReport({ hasError: true });
const result = getters.groupedSecretScanningText(report);
expect(result).toStrictEqual({ message: messages.SECRET_SCANNING_HAS_ERROR });
});
it("should return the loading message if it's still loading", () => {
const report = createReport({ isLoading: true });
const result = getters.groupedSecretScanningText(report);
expect(result).toStrictEqual({ message: messages.SECRET_SCANNING_IS_LOADING });
});
it('should call groupedTextBuilder if everything is fine', () => {
const report = createReport();
const result = getters.groupedSecretScanningText(report);
expect(result).toStrictEqual({
countMessage: '',
critical: 0,
high: 0,
message: 'Secret scanning detected %{totalStart}no%{totalEnd} vulnerabilities.',
other: 0,
status: '',
total: 0,
});
});
});
describe('secretScanningStatusIcon', () => {
it("should return `loading` when we're still loading", () => {
const report = createReport({ isLoading: true });
const result = getters.secretScanningStatusIcon(report);
expect(result).toBe('loading');
});
it("should return `warning` when there's an issue", () => {
const report = createReport({ hasError: true });
const result = getters.secretScanningStatusIcon(report);
expect(result).toBe('warning');
});
it('should return `success` when nothing is wrong', () => {
const report = createReport();
const result = getters.secretScanningStatusIcon(report);
expect(result).toBe('success');
});
});
import * as types from 'ee/vue_shared/security_reports/store/modules/secret_detection/mutation_types';
import createState from 'ee/vue_shared/security_reports/store/modules/secret_detection/state';
import mutations from 'ee/vue_shared/security_reports/store/modules/secret_detection/mutations';
const createIssue = ({ ...config }) => ({ changed: false, ...config });
// See also the corresponding CE specs in
// spec/frontend/vue_shared/security_reports/store/modules/secret_detection/mutations_spec.js
describe('EE secret detection module mutations', () => {
let state;
beforeEach(() => {
state = createState();
});
describe(types.UPDATE_SECRET_SCANNING_ISSUE, () => {
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_SECRET_SCANNING_ISSUE](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_SECRET_SCANNING_ISSUE](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_SECRET_SCANNING_ISSUE](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_SECRET_SCANNING_ISSUE](
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);
});
});
});
});
......@@ -89,14 +89,6 @@ describe('EE sast reports mutations', () => {
});
});
describe('REQUEST_SECRET_SCANNING_DIFF', () => {
it('should set secret scanning loading flag to true', () => {
mutations[types.REQUEST_SECRET_SCANNING_DIFF](stateCopy);
expect(stateCopy.secretScanning.isLoading).toEqual(true);
});
});
describe('SET_ISSUE_MODAL_DATA', () => {
it('has default data', () => {
expect(stateCopy.modal.vulnerability.isDismissed).toEqual(false);
......@@ -486,34 +478,6 @@ describe('EE sast reports mutations', () => {
});
});
describe('UPDATE_SECRET_SCANNING_ISSUE', () => {
it('updates issue in the new issues list', () => {
stateCopy.secretScanning.newIssues = mockFindings;
stateCopy.secretScanning.resolvedIssues = [];
const updatedIssue = {
...mockFindings[0],
foo: 'bar',
};
mutations[types.UPDATE_SECRET_SCANNING_ISSUE](stateCopy, updatedIssue);
expect(stateCopy.secretScanning.newIssues[0]).toEqual(updatedIssue);
});
it('updates issue in the resolved issues list', () => {
stateCopy.secretScanning.newIssues = [];
stateCopy.secretScanning.resolvedIssues = mockFindings;
const updatedIssue = {
...mockFindings[0],
foo: 'bar',
};
mutations[types.UPDATE_SECRET_SCANNING_ISSUE](stateCopy, updatedIssue);
expect(stateCopy.secretScanning.resolvedIssues[0]).toEqual(updatedIssue);
});
});
describe('SET_CONTAINER_SCANNING_DIFF_ENDPOINT', () => {
const endpoint = 'container_scanning_diff_endpoint.json';
......@@ -743,72 +707,6 @@ describe('EE sast reports mutations', () => {
});
});
describe('SET_SECRET_SCANNING_DIFF_ENDPOINT', () => {
const endpoint = 'secret_scanning_diff_endpoint.json';
beforeEach(() => {
mutations[types.SET_SECRET_SCANNING_DIFF_ENDPOINT](stateCopy, endpoint);
});
it('should set the correct endpoint', () => {
expect(stateCopy.secretScanning.paths.diffEndpoint).toEqual(endpoint);
});
});
describe('RECEIVE_SECRET_SCANNING_DIFF_SUCCESS', () => {
const reports = {
diff: {
added: [
{ name: 'added vuln 1', report_type: 'secret_scanning' },
{ name: 'added vuln 2', report_type: 'secret_scanning' },
],
fixed: [{ name: 'fixed vuln 1', report_type: 'secret_scanning' }],
base_report_out_of_date: true,
},
};
beforeEach(() => {
mutations[types.RECEIVE_SECRET_SCANNING_DIFF_SUCCESS](stateCopy, reports);
});
it('should set isLoading to false', () => {
expect(stateCopy.secretScanning.isLoading).toBe(false);
});
it('should set baseReportOutofDate to true', () => {
expect(stateCopy.secretScanning.baseReportOutofDate).toBe(true);
});
it('should parse and set the added vulnerabilities', () => {
reports.diff.added.forEach((vuln, i) => {
expect(stateCopy.secretScanning.newIssues[i]).toMatchObject({
name: vuln.name,
title: vuln.name,
category: vuln.report_type,
});
});
});
it('should parse and set the fixed vulnerabilities', () => {
reports.diff.fixed.forEach((vuln, i) => {
expect(stateCopy.secretScanning.resolvedIssues[i]).toMatchObject({
name: vuln.name,
title: vuln.name,
category: vuln.report_type,
});
});
});
});
describe('RECEIVE_SECRET_SCANNING_DIFF_ERROR', () => {
it('should set secret scanning loading flag to false and error flag to true', () => {
mutations[types.RECEIVE_SECRET_SCANNING_DIFF_ERROR](stateCopy);
expect(stateCopy.secretScanning.isLoading).toEqual(false);
expect(stateCopy.secretScanning.hasError).toEqual(true);
});
});
describe('SET_COVERAGE_FUZZING_DIFF_ENDPOINT', () => {
const endpoint = 'coverage_fuzzing_diff_endpoint.json';
......
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import createState from '~/vue_shared/security_reports/store/modules/secret_detection/state';
import * as types from '~/vue_shared/security_reports/store/modules/secret_detection/mutation_types';
import * as actions from '~/vue_shared/security_reports/store/modules/secret_detection/actions';
import axios from '~/lib/utils/axios_utils';
const diffEndpoint = 'diff-endpoint.json';
const blobPath = 'blob-path.json';
const reports = {
base: 'base',
head: 'head',
enrichData: 'enrichData',
diff: 'diff',
};
const error = 'Something went wrong';
const vulnerabilityFeedbackPath = 'vulnerability-feedback-path';
const rootState = { vulnerabilityFeedbackPath, blobPath };
let state;
describe('secret detection report actions', () => {
beforeEach(() => {
state = createState();
});
describe('setSecretScanningDiffEndpoint', () => {
it(`should commit ${types.SET_SECRET_SCANNING_DIFF_ENDPOINT} with the correct path`, done => {
testAction(
actions.setSecretScanningDiffEndpoint,
diffEndpoint,
state,
[
{
type: types.SET_SECRET_SCANNING_DIFF_ENDPOINT,
payload: diffEndpoint,
},
],
[],
done,
);
});
});
describe('requestSecretScanningDiff', () => {
it(`should commit ${types.REQUEST_SECRET_SCANNING_DIFF}`, done => {
testAction(
actions.requestSecretScanningDiff,
{},
state,
[{ type: types.REQUEST_SECRET_SCANNING_DIFF }],
[],
done,
);
});
});
describe('receiveSecretScanningDiffSuccess', () => {
it(`should commit ${types.RECEIVE_SECRET_SCANNING_DIFF_SUCCESS} with the correct response`, done => {
testAction(
actions.receiveSecretScanningDiffSuccess,
reports,
state,
[
{
type: types.RECEIVE_SECRET_SCANNING_DIFF_SUCCESS,
payload: reports,
},
],
[],
done,
);
});
});
describe('receiveSecretScanningDiffError', () => {
it(`should commit ${types.RECEIVE_SECRET_SCANNING_DIFF_ERROR} with the correct response`, done => {
testAction(
actions.receiveSecretScanningDiffError,
error,
state,
[
{
type: types.RECEIVE_SECRET_SCANNING_DIFF_ERROR,
payload: error,
},
],
[],
done,
);
});
});
describe('fetchSecretScanningDiff', () => {
let mock;
beforeEach(() => {
mock = new MockAdapter(axios);
state.paths.diffEndpoint = diffEndpoint;
rootState.canReadVulnerabilityFeedback = true;
});
afterEach(() => {
mock.restore();
});
describe('when diff and vulnerability feedback endpoints respond successfully', () => {
beforeEach(() => {
mock
.onGet(diffEndpoint)
.replyOnce(200, reports.diff)
.onGet(vulnerabilityFeedbackPath)
.replyOnce(200, reports.enrichData);
});
it('should dispatch the `receiveDiffSuccess` action', done => {
const { diff, enrichData } = reports;
testAction(
actions.fetchSecretScanningDiff,
{},
{ ...rootState, ...state },
[],
[
{ type: 'requestSecretScanningDiff' },
{
type: 'receiveSecretScanningDiffSuccess',
payload: {
diff,
enrichData,
},
},
],
done,
);
});
});
describe('when diff endpoint responds successfully and fetching vulnerability feedback is not authorized', () => {
beforeEach(() => {
rootState.canReadVulnerabilityFeedback = false;
mock.onGet(diffEndpoint).replyOnce(200, reports.diff);
});
it('should dispatch the `receiveSecretScanningDiffSuccess` action with empty enrich data', done => {
const { diff } = reports;
const enrichData = [];
testAction(
actions.fetchSecretScanningDiff,
{},
{ ...rootState, ...state },
[],
[
{ type: 'requestSecretScanningDiff' },
{
type: 'receiveSecretScanningDiffSuccess',
payload: {
diff,
enrichData,
},
},
],
done,
);
});
});
describe('when the vulnerability feedback endpoint fails', () => {
beforeEach(() => {
mock
.onGet(diffEndpoint)
.replyOnce(200, reports.diff)
.onGet(vulnerabilityFeedbackPath)
.replyOnce(404);
});
it('should dispatch the `receiveSecretScanningDiffError` action', done => {
testAction(
actions.fetchSecretScanningDiff,
{},
{ ...rootState, ...state },
[],
[{ type: 'requestSecretScanningDiff' }, { type: 'receiveSecretScanningDiffError' }],
done,
);
});
});
describe('when the diff endpoint fails', () => {
beforeEach(() => {
mock
.onGet(diffEndpoint)
.replyOnce(404)
.onGet(vulnerabilityFeedbackPath)
.replyOnce(200, reports.enrichData);
});
it('should dispatch the `receiveSecretScanningDiffError` action', done => {
testAction(
actions.fetchSecretScanningDiff,
{},
{ ...rootState, ...state },
[],
[{ type: 'requestSecretScanningDiff' }, { type: 'receiveSecretScanningDiffError' }],
done,
);
});
});
});
});
import * as types from '~/vue_shared/security_reports/store/modules/secret_detection/mutation_types';
import createState from '~/vue_shared/security_reports/store/modules/secret_detection/state';
import mutations from '~/vue_shared/security_reports/store/modules/secret_detection/mutations';
const createIssue = ({ ...config }) => ({ changed: false, ...config });
describe('secret detection module mutations', () => {
const path = 'path';
let state;
beforeEach(() => {
state = createState();
});
describe(types.SET_SECRET_SCANNING_DIFF_ENDPOINT, () => {
it('should set the secret detection diff endpoint', () => {
mutations[types.SET_SECRET_SCANNING_DIFF_ENDPOINT](state, path);
expect(state.paths.diffEndpoint).toBe(path);
});
});
describe(types.REQUEST_SECRET_SCANNING_DIFF, () => {
it('should set the `isLoading` status to `true`', () => {
mutations[types.REQUEST_SECRET_SCANNING_DIFF](state);
expect(state.isLoading).toBe(true);
});
});
describe(types.RECEIVE_SECRET_SCANNING_DIFF_SUCCESS, () => {
beforeEach(() => {
const reports = {
diff: {
added: [
createIssue({ cve: 'CVE-1' }),
createIssue({ cve: 'CVE-2' }),
createIssue({ cve: 'CVE-3' }),
],
fixed: [createIssue({ cve: 'CVE-4' }), createIssue({ cve: 'CVE-5' })],
existing: [createIssue({ cve: 'CVE-6' })],
base_report_out_of_date: true,
},
};
state.isLoading = true;
mutations[types.RECEIVE_SECRET_SCANNING_DIFF_SUCCESS](state, reports);
});
it('should set the `isLoading` status to `false`', () => {
expect(state.isLoading).toBe(false);
});
it('should set the `baseReportOutofDate` status to `false`', () => {
expect(state.baseReportOutofDate).toBe(true);
});
it('should have the relevant `new` issues', () => {
expect(state.newIssues).toHaveLength(3);
});
it('should have the relevant `resolved` issues', () => {
expect(state.resolvedIssues).toHaveLength(2);
});
it('should have the relevant `all` issues', () => {
expect(state.allIssues).toHaveLength(1);
});
});
describe(types.RECEIVE_SECRET_SCANNING_DIFF_ERROR, () => {
beforeEach(() => {
state.isLoading = true;
mutations[types.RECEIVE_SECRET_SCANNING_DIFF_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);
});
});
});
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