diff options
Diffstat (limited to 'spec/frontend/vue_shared/security_reports/security_reports_app_spec.js')
-rw-r--r-- | spec/frontend/vue_shared/security_reports/security_reports_app_spec.js | 373 |
1 files changed, 267 insertions, 106 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 ab87d80b291..d11b519e5ad 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 @@ -1,13 +1,32 @@ -import { mount } from '@vue/test-utils'; +import { mount, createLocalVue } from '@vue/test-utils'; +import MockAdapter from 'axios-mock-adapter'; +import { merge } from 'lodash'; +import Vuex from 'vuex'; +import { trimText } from 'helpers/text_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import { + sastDiffSuccessMock, + secretScanningDiffSuccessMock, +} from 'jest/vue_shared/security_reports/mock_data'; import Api from '~/api'; -import Flash from '~/flash'; +import createFlash from '~/flash'; +import axios from '~/lib/utils/axios_utils'; +import { + REPORT_TYPE_SAST, + REPORT_TYPE_SECRET_DETECTION, +} from '~/vue_shared/security_reports/constants'; import SecurityReportsApp from '~/vue_shared/security_reports/security_reports_app.vue'; jest.mock('~/flash'); +const localVue = createLocalVue(); +localVue.use(Vuex); + +const SAST_COMPARISON_PATH = '/sast.json'; +const SECRET_SCANNING_COMPARISON_PATH = '/secret_detection.json'; + describe('Security reports app', () => { let wrapper; - let mrTabsMock; const props = { pipelineId: 123, @@ -15,148 +34,290 @@ describe('Security reports app', () => { securityReportsDocsPath: '/docs', }; - const createComponent = () => { - wrapper = mount(SecurityReportsApp, { - propsData: { ...props }, - }); + const createComponent = options => { + wrapper = mount( + SecurityReportsApp, + merge( + { + localVue, + propsData: { ...props }, + }, + options, + ), + ); }; const anyParams = expect.any(Object); const findPipelinesTabAnchor = () => wrapper.find('[data-testid="show-pipelines"]'); const findHelpLink = () => wrapper.find('[data-testid="help"]'); - const setupMrTabsMock = () => { - mrTabsMock = { tabShown: jest.fn() }; - window.mrTabs = mrTabsMock; - }; const setupMockJobArtifact = reportType => { jest .spyOn(Api, 'pipelineJobs') .mockResolvedValue({ data: [{ artifacts: [{ file_type: reportType }] }] }); }; + const expectPipelinesTabAnchor = () => { + const mrTabsMock = { tabShown: jest.fn() }; + window.mrTabs = mrTabsMock; + findPipelinesTabAnchor().trigger('click'); + expect(mrTabsMock.tabShown.mock.calls).toEqual([['pipelines']]); + }; afterEach(() => { wrapper.destroy(); delete window.mrTabs; }); - describe.each(SecurityReportsApp.reportTypes)('given a report type %p', reportType => { - beforeEach(() => { - window.mrTabs = { tabShown: jest.fn() }; - setupMockJobArtifact(reportType); - createComponent(); - return wrapper.vm.$nextTick(); - }); + describe.each([false, true])( + 'given the coreSecurityMrWidgetCounts feature flag is %p', + coreSecurityMrWidgetCounts => { + const createComponentWithFlag = options => + createComponent( + merge( + { + provide: { + glFeatures: { + coreSecurityMrWidgetCounts, + }, + }, + }, + options, + ), + ); - it('calls the pipelineJobs API correctly', () => { - expect(Api.pipelineJobs).toHaveBeenCalledTimes(1); - expect(Api.pipelineJobs).toHaveBeenCalledWith(props.projectId, props.pipelineId, anyParams); - }); + describe.each(SecurityReportsApp.reportTypes)('given a report type %p', reportType => { + beforeEach(() => { + window.mrTabs = { tabShown: jest.fn() }; + setupMockJobArtifact(reportType); + createComponentWithFlag(); + return wrapper.vm.$nextTick(); + }); - it('renders the expected message', () => { - expect(wrapper.text()).toMatchInterpolatedText(SecurityReportsApp.i18n.scansHaveRun); - }); + it('calls the pipelineJobs API correctly', () => { + expect(Api.pipelineJobs).toHaveBeenCalledTimes(1); + expect(Api.pipelineJobs).toHaveBeenCalledWith( + props.projectId, + props.pipelineId, + anyParams, + ); + }); - describe('clicking the anchor to the pipelines tab', () => { - beforeEach(() => { - setupMrTabsMock(); - findPipelinesTabAnchor().trigger('click'); + it('renders the expected message', () => { + expect(wrapper.text()).toMatchInterpolatedText(SecurityReportsApp.i18n.scansHaveRun); + }); + + describe('clicking the anchor to the pipelines tab', () => { + it('calls the mrTabs.tabShown global', () => { + expectPipelinesTabAnchor(); + }); + }); + + it('renders a help link', () => { + expect(findHelpLink().attributes()).toMatchObject({ + href: props.securityReportsDocsPath, + }); + }); }); - it('calls the mrTabs.tabShown global', () => { - expect(mrTabsMock.tabShown.mock.calls).toEqual([['pipelines']]); + describe('given a report type "foo"', () => { + beforeEach(() => { + setupMockJobArtifact('foo'); + createComponentWithFlag(); + return wrapper.vm.$nextTick(); + }); + + it('calls the pipelineJobs API correctly', () => { + expect(Api.pipelineJobs).toHaveBeenCalledTimes(1); + expect(Api.pipelineJobs).toHaveBeenCalledWith( + props.projectId, + props.pipelineId, + anyParams, + ); + }); + + it('renders nothing', () => { + expect(wrapper.html()).toBe(''); + }); }); - }); - it('renders a help link', () => { - expect(findHelpLink().attributes()).toMatchObject({ - href: props.securityReportsDocsPath, + 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'); + }); + + createComponentWithFlag(); + 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 a report type "foo"', () => { - beforeEach(() => { - setupMockJobArtifact('foo'); - createComponent(); - return wrapper.vm.$nextTick(); - }); + describe('given an error from the API', () => { + let error; - it('calls the pipelineJobs API correctly', () => { - expect(Api.pipelineJobs).toHaveBeenCalledTimes(1); - expect(Api.pipelineJobs).toHaveBeenCalledWith(props.projectId, props.pipelineId, anyParams); - }); + beforeEach(() => { + error = new Error('an error'); + jest.spyOn(Api, 'pipelineJobs').mockRejectedValue(error); + createComponentWithFlag(); + return wrapper.vm.$nextTick(); + }); - it('renders nothing', () => { - expect(wrapper.html()).toBe(''); - }); - }); + it('calls the pipelineJobs API correctly', () => { + expect(Api.pipelineJobs).toHaveBeenCalledTimes(1); + expect(Api.pipelineJobs).toHaveBeenCalledWith( + props.projectId, + props.pipelineId, + anyParams, + ); + }); + + it('renders nothing', () => { + expect(wrapper.html()).toBe(''); + }); + + it('calls createFlash correctly', () => { + expect(createFlash.mock.calls).toEqual([ + [ + { + message: SecurityReportsApp.i18n.apiError, + captureError: true, + error, + }, + ], + ]); + }); + }); + }, + ); - describe('security artifacts on last page of multi-page response', () => { - const numPages = 3; + describe('given the coreSecurityMrWidgetCounts feature flag is enabled', () => { + let mock; + + const createComponentWithFlagEnabled = options => + createComponent( + merge(options, { + provide: { + glFeatures: { + coreSecurityMrWidgetCounts: true, + }, + }, + }), + ); 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(); + mock = new MockAdapter(axios); }); - it('fetches all pages', () => { - expect(Api.pipelineJobs).toHaveBeenCalledTimes(numPages); + afterEach(() => { + mock.restore(); }); - it('renders the expected message', () => { - expect(wrapper.text()).toMatchInterpolatedText(SecurityReportsApp.i18n.scansHaveRun); - }); - }); + const SAST_SUCCESS_MESSAGE = + 'Security scanning detected 1 potential vulnerability 1 Critical 0 High and 0 Others'; + const SECRET_SCANNING_SUCCESS_MESSAGE = + 'Security scanning detected 2 potential vulnerabilities 1 Critical 1 High and 0 Others'; + describe.each` + reportType | pathProp | path | successResponse | successMessage + ${REPORT_TYPE_SAST} | ${'sastComparisonPath'} | ${SAST_COMPARISON_PATH} | ${sastDiffSuccessMock} | ${SAST_SUCCESS_MESSAGE} + ${REPORT_TYPE_SECRET_DETECTION} | ${'secretScanningComparisonPath'} | ${SECRET_SCANNING_COMPARISON_PATH} | ${secretScanningDiffSuccessMock} | ${SECRET_SCANNING_SUCCESS_MESSAGE} + `( + 'given a $pathProp and $reportType artifact', + ({ reportType, pathProp, path, successResponse, successMessage }) => { + beforeEach(() => { + setupMockJobArtifact(reportType); + }); - describe('given an error from the API', () => { - let error; + describe('when loading', () => { + beforeEach(() => { + mock = new MockAdapter(axios, { delayResponse: 1 }); + mock.onGet(path).replyOnce(200, successResponse); - beforeEach(() => { - error = new Error('an error'); - jest.spyOn(Api, 'pipelineJobs').mockRejectedValue(error); - createComponent(); - return wrapper.vm.$nextTick(); - }); + createComponentWithFlagEnabled({ + propsData: { + [pathProp]: path, + }, + }); - it('calls the pipelineJobs API correctly', () => { - expect(Api.pipelineJobs).toHaveBeenCalledTimes(1); - expect(Api.pipelineJobs).toHaveBeenCalledWith(props.projectId, props.pipelineId, anyParams); - }); + return waitForPromises(); + }); - it('renders nothing', () => { - expect(wrapper.html()).toBe(''); - }); + it('should have loading message', () => { + expect(wrapper.text()).toBe('Security scanning is loading'); + }); - it('calls Flash correctly', () => { - expect(Flash.mock.calls).toEqual([ - [ - { - message: SecurityReportsApp.i18n.apiError, - captureError: true, - error, - }, - ], - ]); - }); + it('should not render the pipeline tab anchor', () => { + expect(findPipelinesTabAnchor().exists()).toBe(false); + }); + }); + + describe('when successfully loaded', () => { + beforeEach(() => { + mock.onGet(path).replyOnce(200, successResponse); + + createComponentWithFlagEnabled({ + propsData: { + [pathProp]: path, + }, + }); + + return waitForPromises(); + }); + + it('should show counts', () => { + expect(trimText(wrapper.text())).toContain(successMessage); + }); + + it('should render the pipeline tab anchor', () => { + expectPipelinesTabAnchor(); + }); + }); + + describe('when an error occurs', () => { + beforeEach(() => { + mock.onGet(path).replyOnce(500); + + createComponentWithFlagEnabled({ + propsData: { + [pathProp]: path, + }, + }); + + return waitForPromises(); + }); + + it('should show error message', () => { + expect(trimText(wrapper.text())).toContain('Loading resulted in an error'); + }); + + it('should render the pipeline tab anchor', () => { + expectPipelinesTabAnchor(); + }); + }); + }, + ); }); }); |