diff options
Diffstat (limited to 'spec/frontend/vue_shared/security_reports')
5 files changed, 622 insertions, 4 deletions
diff --git a/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js b/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js index 31bdfc931ac..ab87d80b291 100644 --- a/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js +++ b/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js @@ -5,7 +5,7 @@ import SecurityReportsApp from '~/vue_shared/security_reports/security_reports_a jest.mock('~/flash'); -describe('Grouped security reports app', () => { +describe('Security reports app', () => { let wrapper; let mrTabsMock; @@ -21,6 +21,8 @@ describe('Grouped security reports app', () => { }); }; + const anyParams = expect.any(Object); + const findPipelinesTabAnchor = () => wrapper.find('[data-testid="show-pipelines"]'); const findHelpLink = () => wrapper.find('[data-testid="help"]'); const setupMrTabsMock = () => { @@ -43,10 +45,12 @@ describe('Grouped security reports app', () => { window.mrTabs = { tabShown: jest.fn() }; setupMockJobArtifact(reportType); createComponent(); + return wrapper.vm.$nextTick(); }); it('calls the pipelineJobs API correctly', () => { - expect(Api.pipelineJobs).toHaveBeenCalledWith(props.projectId, props.pipelineId); + expect(Api.pipelineJobs).toHaveBeenCalledTimes(1); + expect(Api.pipelineJobs).toHaveBeenCalledWith(props.projectId, props.pipelineId, anyParams); }); it('renders the expected message', () => { @@ -75,10 +79,12 @@ describe('Grouped security reports app', () => { beforeEach(() => { setupMockJobArtifact('foo'); createComponent(); + return wrapper.vm.$nextTick(); }); it('calls the pipelineJobs API correctly', () => { - expect(Api.pipelineJobs).toHaveBeenCalledWith(props.projectId, props.pipelineId); + expect(Api.pipelineJobs).toHaveBeenCalledTimes(1); + expect(Api.pipelineJobs).toHaveBeenCalledWith(props.projectId, props.pipelineId, anyParams); }); it('renders nothing', () => { @@ -86,6 +92,42 @@ describe('Grouped security reports app', () => { }); }); + describe('security artifacts on last page of multi-page response', () => { + const numPages = 3; + + beforeEach(() => { + jest + .spyOn(Api, 'pipelineJobs') + .mockImplementation(async (projectId, pipelineId, { page }) => { + const requestedPage = parseInt(page, 10); + if (requestedPage < numPages) { + return { + // Some jobs with no relevant artifacts + data: [{}, {}], + headers: { 'x-next-page': String(requestedPage + 1) }, + }; + } else if (requestedPage === numPages) { + return { + data: [{ artifacts: [{ file_type: SecurityReportsApp.reportTypes[0] }] }], + }; + } + + throw new Error('Test failed due to request of non-existent jobs page'); + }); + + createComponent(); + return wrapper.vm.$nextTick(); + }); + + it('fetches all pages', () => { + expect(Api.pipelineJobs).toHaveBeenCalledTimes(numPages); + }); + + it('renders the expected message', () => { + expect(wrapper.text()).toMatchInterpolatedText(SecurityReportsApp.i18n.scansHaveRun); + }); + }); + describe('given an error from the API', () => { let error; @@ -93,10 +135,12 @@ describe('Grouped security reports app', () => { error = new Error('an error'); jest.spyOn(Api, 'pipelineJobs').mockRejectedValue(error); createComponent(); + return wrapper.vm.$nextTick(); }); it('calls the pipelineJobs API correctly', () => { - expect(Api.pipelineJobs).toHaveBeenCalledWith(props.projectId, props.pipelineId); + expect(Api.pipelineJobs).toHaveBeenCalledTimes(1); + expect(Api.pipelineJobs).toHaveBeenCalledWith(props.projectId, props.pipelineId, anyParams); }); it('renders nothing', () => { diff --git a/spec/frontend/vue_shared/security_reports/store/modules/sast/actions_spec.js b/spec/frontend/vue_shared/security_reports/store/modules/sast/actions_spec.js new file mode 100644 index 00000000000..a11f4e05913 --- /dev/null +++ b/spec/frontend/vue_shared/security_reports/store/modules/sast/actions_spec.js @@ -0,0 +1,203 @@ +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, + ); + }); + }); + }); +}); diff --git a/spec/frontend/vue_shared/security_reports/store/modules/sast/mutations_spec.js b/spec/frontend/vue_shared/security_reports/store/modules/sast/mutations_spec.js new file mode 100644 index 00000000000..fd611f38a34 --- /dev/null +++ b/spec/frontend/vue_shared/security_reports/store/modules/sast/mutations_spec.js @@ -0,0 +1,84 @@ +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); + }); + }); +}); diff --git a/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/actions_spec.js b/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/actions_spec.js new file mode 100644 index 00000000000..bbcdfb5cd99 --- /dev/null +++ b/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/actions_spec.js @@ -0,0 +1,203 @@ +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('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 `receiveDiffError` 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, + ); + }); + }); + }); +}); diff --git a/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/mutations_spec.js b/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/mutations_spec.js new file mode 100644 index 00000000000..13fcc0f47a3 --- /dev/null +++ b/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/mutations_spec.js @@ -0,0 +1,84 @@ +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_DIFF_ENDPOINT, () => { + it('should set the secret detection 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 `true`', () => { + 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); + }); + }); +}); |