Commit 92593b4c authored by Mark Florian's avatar Mark Florian Committed by David O'Regan

Move SAST Vuex module to CE

This moves (most of) the SAST 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 SAST 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.

[issue]: https://gitlab.com/gitlab-org/gitlab/-/issues/273423Co-authored-by: default avatarJannik Lehmann <jlehmann@gitlab.com>
parent ff91161d
import * as types from './mutation_types';
import { fetchDiffData } from '../../utils';
export const setDiffEndpoint = ({ commit }, path) => commit(types.SET_DIFF_ENDPOINT, path);
export const requestDiff = ({ commit }) => commit(types.REQUEST_DIFF);
export const receiveDiffSuccess = ({ commit }, response) =>
commit(types.RECEIVE_DIFF_SUCCESS, response);
export const receiveDiffError = ({ commit }, response) =>
commit(types.RECEIVE_DIFF_ERROR, response);
export const fetchDiff = ({ state, rootState, dispatch }) => {
dispatch('requestDiff');
return fetchDiffData(rootState, state.paths.diffEndpoint, 'sast')
.then(data => {
dispatch('receiveDiffSuccess', data);
})
.catch(() => {
dispatch('receiveDiffError');
});
};
import state from './state';
import mutations from './mutations';
import * as actions from './actions';
export default {
namespaced: true,
state,
mutations,
actions,
};
export const RECEIVE_DIFF_SUCCESS = 'RECEIVE_DIFF_SUCCESS';
export const RECEIVE_DIFF_ERROR = 'RECEIVE_DIFF_ERROR';
export const REQUEST_DIFF = 'REQUEST_DIFF';
export const SET_DIFF_ENDPOINT = 'SET_DIFF_ENDPOINT';
import Vue from 'vue';
import * as types from './mutation_types';
import { parseDiff } from '../../utils';
export default {
[types.SET_DIFF_ENDPOINT](state, path) {
Vue.set(state.paths, 'diffEndpoint', path);
},
[types.REQUEST_DIFF](state) {
state.isLoading = true;
},
[types.RECEIVE_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);
state.isLoading = false;
state.newIssues = added;
state.resolvedIssues = fixed;
state.allIssues = existing;
state.baseReportOutofDate = baseReportOutofDate;
state.hasBaseReport = hasBaseReport;
},
[types.RECEIVE_DIFF_ERROR](state) {
state.isLoading = false;
state.hasError = true;
},
};
export default () => ({
paths: {
head: null,
base: null,
diffEndpoint: null,
},
isLoading: false,
hasError: false,
newIssues: [],
resolvedIssues: [],
allIssues: [],
baseReportOutofDate: false,
hasBaseReport: false,
});
import pollUntilComplete from '~/lib/utils/poll_until_complete';
import axios from '~/lib/utils/axios_utils';
export const fetchDiffData = (state, endpoint, category) => {
const requests = [pollUntilComplete(endpoint)];
if (state.canReadVulnerabilityFeedback) {
requests.push(axios.get(state.vulnerabilityFeedbackPath, { params: { category } }));
}
return Promise.all(requests).then(([diffResponse, enrichResponse]) => ({
diff: diffResponse.data,
enrichData: enrichResponse?.data ?? [],
}));
};
/**
* Returns given vulnerability enriched with the corresponding
* feedback (`dismissal` or `issue` type)
* @param {Object} vulnerability
* @param {Array} feedback
*/
export const enrichVulnerabilityWithFeedback = (vulnerability, feedback = []) =>
feedback
.filter(fb => fb.project_fingerprint === vulnerability.project_fingerprint)
.reduce((vuln, fb) => {
if (fb.feedback_type === 'dismissal') {
return {
...vuln,
isDismissed: true,
dismissalFeedback: fb,
};
}
if (fb.feedback_type === 'issue' && fb.issue_iid) {
return {
...vuln,
hasIssue: true,
issue_feedback: fb,
};
}
if (fb.feedback_type === 'merge_request' && fb.merge_request_iid) {
return {
...vuln,
hasMergeRequest: true,
merge_request_feedback: fb,
};
}
return vuln;
}, vulnerability);
/**
* Generates the added, fixed, and existing vulnerabilities from the API report.
*
* @param {Object} diff The original reports.
* @param {Object} enrichData Feedback data to add to the reports.
* @returns {Object}
*/
export const parseDiff = (diff, enrichData) => {
const enrichVulnerability = vulnerability => ({
...enrichVulnerabilityWithFeedback(vulnerability, enrichData),
category: vulnerability.report_type,
title: vulnerability.message || vulnerability.name,
});
return {
added: diff.added ? diff.added.map(enrichVulnerability) : [],
fixed: diff.fixed ? diff.fixed.map(enrichVulnerability) : [],
existing: diff.existing ? diff.existing.map(enrichVulnerability) : [],
};
};
...@@ -5,6 +5,7 @@ import pollUntilComplete from '~/lib/utils/poll_until_complete'; ...@@ -5,6 +5,7 @@ import pollUntilComplete from '~/lib/utils/poll_until_complete';
import { s__, sprintf } from '~/locale'; import { s__, sprintf } from '~/locale';
import { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl } from '~/lib/utils/url_utility';
import toast from '~/vue_shared/plugins/global_toast'; import toast from '~/vue_shared/plugins/global_toast';
import { fetchDiffData } from '~/vue_shared/security_reports/store/utils';
import * as types from './mutation_types'; import * as types from './mutation_types';
/** /**
...@@ -44,19 +45,6 @@ export const setCreateVulnerabilityFeedbackDismissalPath = ({ commit }, path) => ...@@ -44,19 +45,6 @@ export const setCreateVulnerabilityFeedbackDismissalPath = ({ commit }, path) =>
export const setPipelineId = ({ commit }, id) => commit(types.SET_PIPELINE_ID, id); export const setPipelineId = ({ commit }, id) => commit(types.SET_PIPELINE_ID, id);
export const fetchDiffData = (state, endpoint, category) => {
const requests = [pollUntilComplete(endpoint)];
if (state.canReadVulnerabilityFeedback) {
requests.push(axios.get(state.vulnerabilityFeedbackPath, { params: { category } }));
}
return Promise.all(requests).then(([diffResponse, enrichResponse]) => ({
diff: diffResponse.data,
enrichData: enrichResponse?.data ?? [],
}));
};
/** /**
* CONTAINER SCANNING * CONTAINER SCANNING
*/ */
......
import * as types from './mutation_types'; import * as types from './mutation_types';
import { fetchDiffData } from '../../actions';
export const setDiffEndpoint = ({ commit }, path) => commit(types.SET_DIFF_ENDPOINT, path); export * from '~/vue_shared/security_reports/store/modules/sast/actions';
export const requestDiff = ({ commit }) => commit(types.REQUEST_DIFF);
export const updateVulnerability = ({ commit }, vulnerability) => export const updateVulnerability = ({ commit }, vulnerability) =>
commit(types.UPDATE_VULNERABILITY, vulnerability); commit(types.UPDATE_VULNERABILITY, vulnerability);
export const receiveDiffSuccess = ({ commit }, response) =>
commit(types.RECEIVE_DIFF_SUCCESS, response);
export const receiveDiffError = ({ commit }, response) =>
commit(types.RECEIVE_DIFF_ERROR, response);
export const fetchDiff = ({ state, rootState, dispatch }) => {
dispatch('requestDiff');
return fetchDiffData(rootState, state.paths.diffEndpoint, 'sast')
.then(data => {
dispatch('receiveDiffSuccess', data);
})
.catch(() => {
dispatch('receiveDiffError');
});
};
export const RECEIVE_DIFF_SUCCESS = 'RECEIVE_DIFF_SUCCESS'; export * from '~/vue_shared/security_reports/store/modules/sast/mutation_types';
export const RECEIVE_DIFF_ERROR = 'RECEIVE_DIFF_ERROR';
export const REQUEST_DIFF = 'REQUEST_DIFF';
export const SET_DIFF_ENDPOINT = 'SET_DIFF_ENDPOINT';
export const UPDATE_VULNERABILITY = 'UPDATE_VULNERABILITY'; export const UPDATE_VULNERABILITY = 'UPDATE_VULNERABILITY';
import Vue from 'vue'; import ceMutations from '~/vue_shared/security_reports/store/modules/sast/mutations';
import { findIssueIndex } from '../../utils';
import * as types from './mutation_types'; import * as types from './mutation_types';
import { findIssueIndex, parseDiff } from '../../utils';
export default { export default {
[types.SET_DIFF_ENDPOINT](state, path) { ...ceMutations,
Vue.set(state.paths, 'diffEndpoint', path);
},
[types.REQUEST_DIFF](state) {
state.isLoading = true;
},
[types.RECEIVE_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);
state.isLoading = false;
state.newIssues = added;
state.resolvedIssues = fixed;
state.allIssues = existing;
state.baseReportOutofDate = baseReportOutofDate;
state.hasBaseReport = hasBaseReport;
},
[types.RECEIVE_DIFF_ERROR](state) {
state.isLoading = false;
state.hasError = true;
},
[types.UPDATE_VULNERABILITY](state, issue) { [types.UPDATE_VULNERABILITY](state, issue) {
const newIssuesIndex = findIssueIndex(state.newIssues, issue); const newIssuesIndex = findIssueIndex(state.newIssues, issue);
......
export default () => ({ export { default } from '~/vue_shared/security_reports/store/modules/sast/state';
paths: {
head: null,
base: null,
diffEndpoint: null,
},
isLoading: false,
hasError: false,
newIssues: [],
resolvedIssues: [],
allIssues: [],
baseReportOutofDate: false,
hasBaseReport: false,
});
import Vue from 'vue'; import Vue from 'vue';
import * as types from './mutation_types'; import { parseDiff } from '~/vue_shared/security_reports/store/utils';
import { findIssueIndex, parseDiff } from './utils';
import { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl } from '~/lib/utils/url_utility';
import { findIssueIndex } from './utils';
import * as types from './mutation_types';
export default { export default {
[types.SET_HEAD_BLOB_PATH](state, path) { [types.SET_HEAD_BLOB_PATH](state, path) {
......
...@@ -9,40 +9,6 @@ import { __, n__, sprintf } from '~/locale'; ...@@ -9,40 +9,6 @@ import { __, n__, sprintf } from '~/locale';
export const findIssueIndex = (issues, issue) => export const findIssueIndex = (issues, issue) =>
issues.findIndex(el => el.project_fingerprint === issue.project_fingerprint); issues.findIndex(el => el.project_fingerprint === issue.project_fingerprint);
/**
* Returns given vulnerability enriched with the corresponding
* feedback (`dismissal` or `issue` type)
* @param {Object} vulnerability
* @param {Array} feedback
*/
export const enrichVulnerabilityWithFeedback = (vulnerability, feedback = []) =>
feedback
.filter(fb => fb.project_fingerprint === vulnerability.project_fingerprint)
.reduce((vuln, fb) => {
if (fb.feedback_type === 'dismissal') {
return {
...vuln,
isDismissed: true,
dismissalFeedback: fb,
};
}
if (fb.feedback_type === 'issue' && fb.issue_iid) {
return {
...vuln,
hasIssue: true,
issue_feedback: fb,
};
}
if (fb.feedback_type === 'merge_request' && fb.merge_request_iid) {
return {
...vuln,
hasMergeRequest: true,
merge_request_feedback: fb,
};
}
return vuln;
}, vulnerability);
/** /**
* Takes an object of options and returns an externalized string representing * Takes an object of options and returns an externalized string representing
* the critical, high, and other severity vulnerabilities for a given report. * the critical, high, and other severity vulnerabilities for a given report.
...@@ -202,24 +168,3 @@ export const groupedReportText = (report, reportType, errorMessage, loadingMessa ...@@ -202,24 +168,3 @@ export const groupedReportText = (report, reportType, errorMessage, loadingMessa
...countVulnerabilities(report.newIssues), ...countVulnerabilities(report.newIssues),
}); });
}; };
/**
* Generates the added, fixed, and existing vulnerabilities from the API report.
*
* @param {Object} diff The original reports.
* @param {Object} enrichData Feedback data to add to the reports.
* @returns {Object}
*/
export const parseDiff = (diff, enrichData) => {
const enrichVulnerability = vulnerability => ({
...enrichVulnerabilityWithFeedback(vulnerability, enrichData),
category: vulnerability.report_type,
title: vulnerability.message || vulnerability.name,
});
return {
added: diff.added ? diff.added.map(enrichVulnerability) : [],
fixed: diff.fixed ? diff.fixed.map(enrichVulnerability) : [],
existing: diff.existing ? diff.existing.map(enrichVulnerability) : [],
};
};
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper'; import testAction from 'helpers/vuex_action_helper';
import createState from 'ee/vue_shared/security_reports/store/modules/sast/state'; 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 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'; import * as actions from 'ee/vue_shared/security_reports/store/modules/sast/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 issue = {}; const issue = {};
const vulnerabilityFeedbackPath = 'vulnerability-feedback-path';
const rootState = { vulnerabilityFeedbackPath, blobPath };
let state; let state;
describe('sast report actions', () => { // See also the corresponding CE specs in
// spec/frontend/vue_shared/security_reports/store/modules/sast/actions_spec.js
describe('EE sast report actions', () => {
beforeEach(() => { beforeEach(() => {
state = createState(); state = createState();
}); });
describe('setDiffEndpoint', () => {
it(`should commit ${types.SET_DIFF_ENDPOINT} with the correct path`, done => {
testAction(
actions.setDiffEndpoint,
diffEndpoint,
state,
[
{
type: types.SET_DIFF_ENDPOINT,
payload: diffEndpoint,
},
],
[],
done,
);
});
});
describe('requestDiff', () => {
it(`should commit ${types.REQUEST_DIFF}`, done => {
testAction(actions.requestDiff, {}, state, [{ type: types.REQUEST_DIFF }], [], done);
});
});
describe('receiveDiffSuccess', () => {
it(`should commit ${types.RECEIVE_DIFF_SUCCESS} with the correct response`, done => {
testAction(
actions.receiveDiffSuccess,
reports,
state,
[
{
type: types.RECEIVE_DIFF_SUCCESS,
payload: reports,
},
],
[],
done,
);
});
});
describe('receiveDiffError', () => {
it(`should commit ${types.RECEIVE_DIFF_ERROR} with the correct response`, done => {
testAction(
actions.receiveDiffError,
error,
state,
[
{
type: types.RECEIVE_DIFF_ERROR,
payload: error,
},
],
[],
done,
);
});
});
describe('fetchDiff', () => {
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.fetchDiff,
{},
{ ...rootState, ...state },
[],
[
{ type: 'requestDiff' },
{
type: 'receiveDiffSuccess',
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 `receiveDiffSuccess` action with empty enrich data', done => {
const { diff } = reports;
const enrichData = [];
testAction(
actions.fetchDiff,
{},
{ ...rootState, ...state },
[],
[
{ type: 'requestDiff' },
{
type: 'receiveDiffSuccess',
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 `receiveError` action', done => {
testAction(
actions.fetchDiff,
{},
{ ...rootState, ...state },
[],
[{ type: 'requestDiff' }, { type: 'receiveDiffError' }],
done,
);
});
});
describe('when the diff endpoint fails', () => {
beforeEach(() => {
mock
.onGet(diffEndpoint)
.replyOnce(404)
.onGet(vulnerabilityFeedbackPath)
.replyOnce(200, reports.enrichData);
});
it('should dispatch the `receiveDiffError` action', done => {
testAction(
actions.fetchDiff,
{},
{ ...rootState, ...state },
[],
[{ type: 'requestDiff' }, { type: 'receiveDiffError' }],
done,
);
});
});
});
describe('updateVulnerability', () => { describe('updateVulnerability', () => {
it(`should commit ${types.UPDATE_VULNERABILITY} with the correct response`, done => { it(`should commit ${types.UPDATE_VULNERABILITY} with the correct response`, done => {
testAction( testAction(
......
...@@ -4,30 +4,15 @@ import mutations from 'ee/vue_shared/security_reports/store/modules/sast/mutatio ...@@ -4,30 +4,15 @@ import mutations from 'ee/vue_shared/security_reports/store/modules/sast/mutatio
const createIssue = ({ ...config }) => ({ changed: false, ...config }); const createIssue = ({ ...config }) => ({ changed: false, ...config });
describe('sast module mutations', () => { // See also the corresponding CE specs in
const path = 'path'; // spec/frontend/vue_shared/security_reports/store/modules/sast/mutations_spec.js
describe('EE sast module mutations', () => {
let state; let state;
beforeEach(() => { beforeEach(() => {
state = createState(); state = createState();
}); });
describe(types.SET_DIFF_ENDPOINT, () => {
it('should set the SAST diff endpoint', () => {
mutations[types.SET_DIFF_ENDPOINT](state, path);
expect(state.paths.diffEndpoint).toBe(path);
});
});
describe(types.REQUEST_DIFF, () => {
it('should set the `isLoading` status to `true`', () => {
mutations[types.REQUEST_DIFF](state);
expect(state.isLoading).toBe(true);
});
});
describe(types.UPDATE_VULNERABILITY, () => { describe(types.UPDATE_VULNERABILITY, () => {
let newIssue; let newIssue;
let resolvedIssue; let resolvedIssue;
...@@ -88,58 +73,4 @@ describe('sast module mutations', () => { ...@@ -88,58 +73,4 @@ describe('sast module mutations', () => {
}); });
}); });
}); });
describe(types.RECEIVE_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_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_DIFF_ERROR, () => {
beforeEach(() => {
state.isLoading = true;
mutations[types.RECEIVE_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);
});
});
}); });
...@@ -8,7 +8,9 @@ jest.mock('~/lib/utils/url_utility', () => ({ ...@@ -8,7 +8,9 @@ jest.mock('~/lib/utils/url_utility', () => ({
visitUrl: jest.fn().mockName('visitUrlMock'), visitUrl: jest.fn().mockName('visitUrlMock'),
})); }));
describe('security reports mutations', () => { // See also the corresponding CE specs in
// spec/frontend/vue_shared/security_reports/store/modules/sast/mutations_spec.js
describe('EE sast reports mutations', () => {
let stateCopy; let stateCopy;
beforeEach(() => { beforeEach(() => {
......
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import createState from '~/vue_shared/security_reports/store/modules/sast/state';
import * as types from '~/vue_shared/security_reports/store/modules/sast/mutation_types';
import * as actions from '~/vue_shared/security_reports/store/modules/sast/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('sast report actions', () => {
beforeEach(() => {
state = createState();
});
describe('setDiffEndpoint', () => {
it(`should commit ${types.SET_DIFF_ENDPOINT} with the correct path`, done => {
testAction(
actions.setDiffEndpoint,
diffEndpoint,
state,
[
{
type: types.SET_DIFF_ENDPOINT,
payload: diffEndpoint,
},
],
[],
done,
);
});
});
describe('requestDiff', () => {
it(`should commit ${types.REQUEST_DIFF}`, done => {
testAction(actions.requestDiff, {}, state, [{ type: types.REQUEST_DIFF }], [], done);
});
});
describe('receiveDiffSuccess', () => {
it(`should commit ${types.RECEIVE_DIFF_SUCCESS} with the correct response`, done => {
testAction(
actions.receiveDiffSuccess,
reports,
state,
[
{
type: types.RECEIVE_DIFF_SUCCESS,
payload: reports,
},
],
[],
done,
);
});
});
describe('receiveDiffError', () => {
it(`should commit ${types.RECEIVE_DIFF_ERROR} with the correct response`, done => {
testAction(
actions.receiveDiffError,
error,
state,
[
{
type: types.RECEIVE_DIFF_ERROR,
payload: error,
},
],
[],
done,
);
});
});
describe('fetchDiff', () => {
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.fetchDiff,
{},
{ ...rootState, ...state },
[],
[
{ type: 'requestDiff' },
{
type: 'receiveDiffSuccess',
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 `receiveDiffSuccess` action with empty enrich data', done => {
const { diff } = reports;
const enrichData = [];
testAction(
actions.fetchDiff,
{},
{ ...rootState, ...state },
[],
[
{ type: 'requestDiff' },
{
type: 'receiveDiffSuccess',
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 `receiveError` action', done => {
testAction(
actions.fetchDiff,
{},
{ ...rootState, ...state },
[],
[{ type: 'requestDiff' }, { type: 'receiveDiffError' }],
done,
);
});
});
describe('when the diff endpoint fails', () => {
beforeEach(() => {
mock
.onGet(diffEndpoint)
.replyOnce(404)
.onGet(vulnerabilityFeedbackPath)
.replyOnce(200, reports.enrichData);
});
it('should dispatch the `receiveDiffError` action', done => {
testAction(
actions.fetchDiff,
{},
{ ...rootState, ...state },
[],
[{ type: 'requestDiff' }, { type: 'receiveDiffError' }],
done,
);
});
});
});
});
import * as types from '~/vue_shared/security_reports/store/modules/sast/mutation_types';
import createState from '~/vue_shared/security_reports/store/modules/sast/state';
import mutations from '~/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_DIFF_ENDPOINT, () => {
it('should set the SAST diff endpoint', () => {
mutations[types.SET_DIFF_ENDPOINT](state, path);
expect(state.paths.diffEndpoint).toBe(path);
});
});
describe(types.REQUEST_DIFF, () => {
it('should set the `isLoading` status to `true`', () => {
mutations[types.REQUEST_DIFF](state);
expect(state.isLoading).toBe(true);
});
});
describe(types.RECEIVE_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_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_DIFF_ERROR, () => {
beforeEach(() => {
state.isLoading = true;
mutations[types.RECEIVE_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