diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-19 18:44:42 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-19 18:44:42 +0300 |
commit | 4555e1b21c365ed8303ffb7a3325d773c9b8bf31 (patch) | |
tree | 5423a1c7516cffe36384133ade12572cf709398d /spec/frontend/vue_shared/security_reports | |
parent | e570267f2f6b326480d284e0164a6464ba4081bc (diff) |
Add latest changes from gitlab-org/gitlab@13-12-stable-eev13.12.0-rc42
Diffstat (limited to 'spec/frontend/vue_shared/security_reports')
5 files changed, 335 insertions, 19 deletions
diff --git a/spec/frontend/vue_shared/security_reports/components/apollo_mocks.js b/spec/frontend/vue_shared/security_reports/components/apollo_mocks.js new file mode 100644 index 00000000000..066f9a57bc6 --- /dev/null +++ b/spec/frontend/vue_shared/security_reports/components/apollo_mocks.js @@ -0,0 +1,12 @@ +export const buildConfigureSecurityFeatureMockFactory = (mutationType) => ({ + successPath = 'testSuccessPath', + errors = [], +} = {}) => ({ + data: { + [mutationType]: { + successPath, + errors, + __typename: `${mutationType}Payload`, + }, + }, +}); diff --git a/spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js b/spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js new file mode 100644 index 00000000000..517eee6a729 --- /dev/null +++ b/spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js @@ -0,0 +1,184 @@ +import { GlButton } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import { featureToMutationMap } from 'ee_else_ce/security_configuration/components/constants'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import { humanize } from '~/lib/utils/text_utility'; +import { redirectTo } from '~/lib/utils/url_utility'; +import ManageViaMr from '~/vue_shared/security_configuration/components/manage_via_mr.vue'; +import { buildConfigureSecurityFeatureMockFactory } from './apollo_mocks'; + +jest.mock('~/lib/utils/url_utility'); + +Vue.use(VueApollo); + +const projectPath = 'namespace/project'; + +describe('ManageViaMr component', () => { + let wrapper; + + const findButton = () => wrapper.findComponent(GlButton); + + function createMockApolloProvider(mutation, handler) { + const requestHandlers = [[mutation, handler]]; + + return createMockApollo(requestHandlers); + } + + function createComponent({ + featureName = 'SAST', + featureType = 'sast', + isFeatureConfigured = false, + variant = undefined, + category = undefined, + ...options + } = {}) { + wrapper = extendedWrapper( + mount(ManageViaMr, { + provide: { + projectPath, + }, + propsData: { + feature: { + name: featureName, + type: featureType, + configured: isFeatureConfigured, + }, + variant, + category, + }, + ...options, + }), + ); + } + + afterEach(() => { + wrapper.destroy(); + }); + + // This component supports different report types/mutations depending on + // whether it's in a CE or EE context. This makes sure we are only testing + // the ones available in the current test context. + const supportedReportTypes = Object.entries(featureToMutationMap).map( + ([featureType, { getMutationPayload, mutationId }]) => { + const { mutation, variables: mutationVariables } = getMutationPayload(projectPath); + return [humanize(featureType), featureType, mutation, mutationId, mutationVariables]; + }, + ); + + describe.each(supportedReportTypes)( + '%s', + (featureName, featureType, mutation, mutationId, mutationVariables) => { + const buildConfigureSecurityFeatureMock = buildConfigureSecurityFeatureMockFactory( + mutationId, + ); + const successHandler = jest.fn(async () => buildConfigureSecurityFeatureMock()); + const noSuccessPathHandler = async () => + buildConfigureSecurityFeatureMock({ + successPath: '', + }); + const errorHandler = async () => + buildConfigureSecurityFeatureMock({ + errors: ['foo'], + }); + const pendingHandler = () => new Promise(() => {}); + + describe('when feature is configured', () => { + beforeEach(() => { + const apolloProvider = createMockApolloProvider(mutation, successHandler); + createComponent({ apolloProvider, featureName, featureType, isFeatureConfigured: true }); + }); + + it('it does not render a button', () => { + expect(findButton().exists()).toBe(false); + }); + }); + + describe('when feature is not configured', () => { + beforeEach(() => { + const apolloProvider = createMockApolloProvider(mutation, successHandler); + createComponent({ apolloProvider, featureName, featureType, isFeatureConfigured: false }); + }); + + it('it does render a button', () => { + expect(findButton().exists()).toBe(true); + }); + + it('clicking on the button triggers the configure mutation', () => { + findButton().trigger('click'); + + expect(successHandler).toHaveBeenCalledTimes(1); + expect(successHandler).toHaveBeenCalledWith(mutationVariables); + }); + }); + + describe('given a pending response', () => { + beforeEach(() => { + const apolloProvider = createMockApolloProvider(mutation, pendingHandler); + createComponent({ apolloProvider, featureName, featureType }); + }); + + it('renders spinner correctly', async () => { + const button = findButton(); + expect(button.props('loading')).toBe(false); + await button.trigger('click'); + expect(button.props('loading')).toBe(true); + }); + }); + + describe('given a successful response', () => { + beforeEach(() => { + const apolloProvider = createMockApolloProvider(mutation, successHandler); + createComponent({ apolloProvider, featureName, featureType }); + }); + + it('should call redirect helper with correct value', async () => { + await wrapper.trigger('click'); + await waitForPromises(); + expect(redirectTo).toHaveBeenCalledTimes(1); + expect(redirectTo).toHaveBeenCalledWith('testSuccessPath'); + // This is done for UX reasons. If the loading prop is set to false + // on success, then there's a period where the button is clickable + // again. Instead, we want the button to display a loading indicator + // for the remainder of the lifetime of the page (i.e., until the + // browser can start painting the new page it's been redirected to). + expect(findButton().props().loading).toBe(true); + }); + }); + + describe.each` + handler | message + ${noSuccessPathHandler} | ${`${featureName} merge request creation mutation failed`} + ${errorHandler} | ${'foo'} + `('given an error response', ({ handler, message }) => { + beforeEach(() => { + const apolloProvider = createMockApolloProvider(mutation, handler); + createComponent({ apolloProvider, featureName, featureType }); + }); + + it('should catch and emit error', async () => { + await wrapper.trigger('click'); + await waitForPromises(); + expect(wrapper.emitted('error')).toEqual([[message]]); + expect(findButton().props('loading')).toBe(false); + }); + }); + }, + ); + + describe('button props', () => { + it('passes the variant and category props to the GlButton', () => { + const variant = 'danger'; + const category = 'tertiary'; + createComponent({ variant, category }); + + expect(wrapper.findComponent(GlButton).props()).toMatchObject({ + variant, + category, + }); + }); + }); +}); diff --git a/spec/frontend/vue_shared/security_reports/mock_data.js b/spec/frontend/vue_shared/security_reports/mock_data.js index 7918f70d702..bd9ce3b7314 100644 --- a/spec/frontend/vue_shared/security_reports/mock_data.js +++ b/spec/frontend/vue_shared/security_reports/mock_data.js @@ -322,7 +322,7 @@ export const secretScanningDiffSuccessMock = { head_report_created_at: '2020-01-10T10:00:00.000Z', }; -export const securityReportDownloadPathsQueryNoArtifactsResponse = { +export const securityReportMergeRequestDownloadPathsQueryNoArtifactsResponse = { project: { mergeRequest: { headPipeline: { @@ -339,7 +339,7 @@ export const securityReportDownloadPathsQueryNoArtifactsResponse = { }, }; -export const securityReportDownloadPathsQueryResponse = { +export const securityReportMergeRequestDownloadPathsQueryResponse = { project: { mergeRequest: { headPipeline: { @@ -447,8 +447,114 @@ export const securityReportDownloadPathsQueryResponse = { }, }; +export const securityReportPipelineDownloadPathsQueryResponse = { + project: { + pipeline: { + id: 'gid://gitlab/Ci::Pipeline/176', + jobs: { + nodes: [ + { + name: 'secret_detection', + artifacts: { + nodes: [ + { + downloadPath: + '/gitlab-org/secrets-detection-test/-/jobs/1399/artifacts/download?file_type=trace', + fileType: 'TRACE', + __typename: 'CiJobArtifact', + }, + { + downloadPath: + '/gitlab-org/secrets-detection-test/-/jobs/1399/artifacts/download?file_type=secret_detection', + fileType: 'SECRET_DETECTION', + __typename: 'CiJobArtifact', + }, + ], + __typename: 'CiJobArtifactConnection', + }, + __typename: 'CiJob', + }, + { + name: 'bandit-sast', + artifacts: { + nodes: [ + { + downloadPath: + '/gitlab-org/secrets-detection-test/-/jobs/1400/artifacts/download?file_type=trace', + fileType: 'TRACE', + __typename: 'CiJobArtifact', + }, + { + downloadPath: + '/gitlab-org/secrets-detection-test/-/jobs/1400/artifacts/download?file_type=sast', + fileType: 'SAST', + __typename: 'CiJobArtifact', + }, + ], + __typename: 'CiJobArtifactConnection', + }, + __typename: 'CiJob', + }, + { + name: 'eslint-sast', + artifacts: { + nodes: [ + { + downloadPath: + '/gitlab-org/secrets-detection-test/-/jobs/1401/artifacts/download?file_type=trace', + fileType: 'TRACE', + __typename: 'CiJobArtifact', + }, + { + downloadPath: + '/gitlab-org/secrets-detection-test/-/jobs/1401/artifacts/download?file_type=sast', + fileType: 'SAST', + __typename: 'CiJobArtifact', + }, + ], + __typename: 'CiJobArtifactConnection', + }, + __typename: 'CiJob', + }, + { + name: 'all_artifacts', + artifacts: { + nodes: [ + { + downloadPath: + '/gitlab-org/secrets-detection-test/-/jobs/1402/artifacts/download?file_type=archive', + fileType: 'ARCHIVE', + __typename: 'CiJobArtifact', + }, + { + downloadPath: + '/gitlab-org/secrets-detection-test/-/jobs/1402/artifacts/download?file_type=trace', + fileType: 'TRACE', + __typename: 'CiJobArtifact', + }, + { + downloadPath: + '/gitlab-org/secrets-detection-test/-/jobs/1402/artifacts/download?file_type=metadata', + fileType: 'METADATA', + __typename: 'CiJobArtifact', + }, + ], + __typename: 'CiJobArtifactConnection', + }, + __typename: 'CiJob', + }, + ], + __typename: 'CiJobConnection', + }, + __typename: 'Pipeline', + }, + __typename: 'MergeRequest', + }, + __typename: 'Project', +}; + /** - * These correspond to SAST jobs in the securityReportDownloadPathsQueryResponse above. + * These correspond to SAST jobs in the securityReportMergeRequestDownloadPathsQueryResponse above. */ export const sastArtifacts = [ { @@ -464,7 +570,7 @@ export const sastArtifacts = [ ]; /** - * These correspond to Secret Detection jobs in the securityReportDownloadPathsQueryResponse above. + * These correspond to Secret Detection jobs in the securityReportMergeRequestDownloadPathsQueryResponse above. */ export const secretDetectionArtifacts = [ { @@ -481,7 +587,7 @@ export const expectedDownloadDropdownProps = { }; /** - * These correspond to any jobs with zip archives in the securityReportDownloadPathsQueryResponse above. + * These correspond to any jobs with zip archives in the securityReportMergeRequestDownloadPathsQueryResponse above. */ export const archiveArtifacts = [ { @@ -492,7 +598,7 @@ export const archiveArtifacts = [ ]; /** - * These correspond to any jobs with trace data in the securityReportDownloadPathsQueryResponse above. + * These correspond to any jobs with trace data in the securityReportMergeRequestDownloadPathsQueryResponse above. */ export const traceArtifacts = [ { @@ -518,7 +624,7 @@ export const traceArtifacts = [ ]; /** - * These correspond to any jobs with metadata data in the securityReportDownloadPathsQueryResponse above. + * These correspond to any jobs with metadata data in the securityReportMergeRequestDownloadPathsQueryResponse above. */ export const metadataArtifacts = [ { 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 0b4816a951e..038d7754776 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 @@ -9,8 +9,8 @@ import { trimText } from 'helpers/text_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { expectedDownloadDropdownProps, - securityReportDownloadPathsQueryNoArtifactsResponse, - securityReportDownloadPathsQueryResponse, + securityReportMergeRequestDownloadPathsQueryNoArtifactsResponse, + securityReportMergeRequestDownloadPathsQueryResponse, sastDiffSuccessMock, secretScanningDiffSuccessMock, } from 'jest/vue_shared/security_reports/mock_data'; @@ -22,7 +22,7 @@ import { REPORT_TYPE_SAST, REPORT_TYPE_SECRET_DETECTION, } from '~/vue_shared/security_reports/constants'; -import securityReportDownloadPathsQuery from '~/vue_shared/security_reports/queries/security_report_download_paths.query.graphql'; +import securityReportMergeRequestDownloadPathsQuery from '~/vue_shared/security_reports/queries/security_report_merge_request_download_paths.query.graphql'; import SecurityReportsApp from '~/vue_shared/security_reports/security_reports_app.vue'; jest.mock('~/flash'); @@ -59,12 +59,13 @@ describe('Security reports app', () => { }; const pendingHandler = () => new Promise(() => {}); - const successHandler = () => Promise.resolve({ data: securityReportDownloadPathsQueryResponse }); + const successHandler = () => + Promise.resolve({ data: securityReportMergeRequestDownloadPathsQueryResponse }); const successEmptyHandler = () => - Promise.resolve({ data: securityReportDownloadPathsQueryNoArtifactsResponse }); + Promise.resolve({ data: securityReportMergeRequestDownloadPathsQueryNoArtifactsResponse }); const failureHandler = () => Promise.resolve({ errors: [{ message: 'some error' }] }); const createMockApolloProvider = (handler) => { - const requestHandlers = [[securityReportDownloadPathsQuery, handler]]; + const requestHandlers = [[securityReportMergeRequestDownloadPathsQuery, handler]]; return createMockApollo(requestHandlers); }; diff --git a/spec/frontend/vue_shared/security_reports/utils_spec.js b/spec/frontend/vue_shared/security_reports/utils_spec.js index aa9e54fa10c..b7129ece698 100644 --- a/spec/frontend/vue_shared/security_reports/utils_spec.js +++ b/spec/frontend/vue_shared/security_reports/utils_spec.js @@ -3,9 +3,13 @@ import { REPORT_TYPE_SECRET_DETECTION, REPORT_FILE_TYPES, } from '~/vue_shared/security_reports/constants'; -import { extractSecurityReportArtifacts } from '~/vue_shared/security_reports/utils'; import { - securityReportDownloadPathsQueryResponse, + extractSecurityReportArtifactsFromMergeRequest, + extractSecurityReportArtifactsFromPipeline, +} from '~/vue_shared/security_reports/utils'; +import { + securityReportMergeRequestDownloadPathsQueryResponse, + securityReportPipelineDownloadPathsQueryResponse, sastArtifacts, secretDetectionArtifacts, archiveArtifacts, @@ -13,7 +17,18 @@ import { metadataArtifacts, } from './mock_data'; -describe('extractSecurityReportArtifacts', () => { +describe.each([ + [ + 'extractSecurityReportArtifactsFromMergeRequest', + extractSecurityReportArtifactsFromMergeRequest, + securityReportMergeRequestDownloadPathsQueryResponse, + ], + [ + 'extractSecurityReportArtifactsFromPipelines', + extractSecurityReportArtifactsFromPipeline, + securityReportPipelineDownloadPathsQueryResponse, + ], +])('%s', (funcName, extractFunc, response) => { it.each` reportTypes | expectedArtifacts ${[]} | ${[]} @@ -27,9 +42,7 @@ describe('extractSecurityReportArtifacts', () => { `( 'returns the expected artifacts given report types $reportTypes', ({ reportTypes, expectedArtifacts }) => { - expect( - extractSecurityReportArtifacts(reportTypes, securityReportDownloadPathsQueryResponse), - ).toEqual(expectedArtifacts); + expect(extractFunc(reportTypes, response)).toEqual(expectedArtifacts); }, ); }); |