diff options
Diffstat (limited to 'spec/frontend/security_configuration')
4 files changed, 422 insertions, 0 deletions
diff --git a/spec/frontend/security_configuration/components/redesigned_app_spec.js b/spec/frontend/security_configuration/components/redesigned_app_spec.js new file mode 100644 index 00000000000..7e27a3e1108 --- /dev/null +++ b/spec/frontend/security_configuration/components/redesigned_app_spec.js @@ -0,0 +1,232 @@ +import { GlTab } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; +import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import { + SAST_NAME, + SAST_SHORT_NAME, + SAST_DESCRIPTION, + SAST_HELP_PATH, + SAST_CONFIG_HELP_PATH, + LICENSE_COMPLIANCE_NAME, + LICENSE_COMPLIANCE_DESCRIPTION, + LICENSE_COMPLIANCE_HELP_PATH, +} from '~/security_configuration/components/constants'; +import FeatureCard from '~/security_configuration/components/feature_card.vue'; +import RedesignedSecurityConfigurationApp, { + i18n, +} from '~/security_configuration/components/redesigned_app.vue'; +import UpgradeBanner from '~/security_configuration/components/upgrade_banner.vue'; +import { + REPORT_TYPE_LICENSE_COMPLIANCE, + REPORT_TYPE_SAST, +} from '~/vue_shared/security_reports/constants'; + +const upgradePath = '/upgrade'; + +describe('redesigned App component', () => { + let wrapper; + let userCalloutDismissSpy; + + const createComponent = ({ shouldShowCallout = true, ...propsData }) => { + userCalloutDismissSpy = jest.fn(); + + wrapper = extendedWrapper( + mount(RedesignedSecurityConfigurationApp, { + propsData, + provide: { + upgradePath, + }, + stubs: { + UserCalloutDismisser: makeMockUserCalloutDismisser({ + dismiss: userCalloutDismissSpy, + shouldShowCallout, + }), + }, + }), + ); + }; + + const findMainHeading = () => wrapper.find('h1'); + const findTab = () => wrapper.findComponent(GlTab); + const findTabs = () => wrapper.findAllComponents(GlTab); + const findByTestId = (id) => wrapper.findByTestId(id); + const findFeatureCards = () => wrapper.findAllComponents(FeatureCard); + const findComplianceViewHistoryLink = () => findByTestId('compliance-view-history-link'); + const findSecurityViewHistoryLink = () => findByTestId('security-view-history-link'); + const findUpgradeBanner = () => wrapper.findComponent(UpgradeBanner); + + const securityFeaturesMock = [ + { + name: SAST_NAME, + shortName: SAST_SHORT_NAME, + description: SAST_DESCRIPTION, + helpPath: SAST_HELP_PATH, + configurationHelpPath: SAST_CONFIG_HELP_PATH, + type: REPORT_TYPE_SAST, + available: true, + }, + ]; + + const complianceFeaturesMock = [ + { + name: LICENSE_COMPLIANCE_NAME, + description: LICENSE_COMPLIANCE_DESCRIPTION, + helpPath: LICENSE_COMPLIANCE_HELP_PATH, + type: REPORT_TYPE_LICENSE_COMPLIANCE, + configurationHelpPath: LICENSE_COMPLIANCE_HELP_PATH, + }, + ]; + + afterEach(() => { + wrapper.destroy(); + }); + + describe('basic structure', () => { + beforeEach(() => { + createComponent({ + augmentedSecurityFeatures: securityFeaturesMock, + augmentedComplianceFeatures: complianceFeaturesMock, + }); + }); + + it('renders main-heading with correct text', () => { + const mainHeading = findMainHeading(); + expect(mainHeading).toExist(); + expect(mainHeading.text()).toContain('Security Configuration'); + }); + + it('renders GlTab Component ', () => { + expect(findTab()).toExist(); + }); + + it('renders right amount of tabs with correct title ', () => { + expect(findTabs()).toHaveLength(2); + }); + + it('renders security-testing tab', () => { + expect(findByTestId('security-testing-tab').exists()).toBe(true); + }); + + it('renders compliance-testing tab', () => { + expect(findByTestId('compliance-testing-tab').exists()).toBe(true); + }); + + it('renders right amount of feature cards for given props with correct props', () => { + const cards = findFeatureCards(); + expect(cards).toHaveLength(2); + expect(cards.at(0).props()).toEqual({ feature: securityFeaturesMock[0] }); + expect(cards.at(1).props()).toEqual({ feature: complianceFeaturesMock[0] }); + }); + + it('should not show latest pipeline link when latestPipelinePath is not defined', () => { + expect(findByTestId('latest-pipeline-info').exists()).toBe(false); + }); + + it('should not show configuration History Link when gitlabCiPresent & gitlabCiHistoryPath are not defined', () => { + expect(findComplianceViewHistoryLink().exists()).toBe(false); + expect(findSecurityViewHistoryLink().exists()).toBe(false); + }); + }); + + describe('upgrade banner', () => { + const makeAvailable = (available) => (feature) => ({ ...feature, available }); + + describe('given at least one unavailable feature', () => { + beforeEach(() => { + createComponent({ + augmentedSecurityFeatures: securityFeaturesMock, + augmentedComplianceFeatures: complianceFeaturesMock.map(makeAvailable(false)), + }); + }); + + it('renders the banner', () => { + expect(findUpgradeBanner().exists()).toBe(true); + }); + + it('calls the dismiss callback when closing the banner', () => { + expect(userCalloutDismissSpy).not.toHaveBeenCalled(); + + findUpgradeBanner().vm.$emit('close'); + + expect(userCalloutDismissSpy).toHaveBeenCalledTimes(1); + }); + }); + + describe('given at least one unavailable feature, but banner is already dismissed', () => { + beforeEach(() => { + createComponent({ + augmentedSecurityFeatures: securityFeaturesMock, + augmentedComplianceFeatures: complianceFeaturesMock.map(makeAvailable(false)), + shouldShowCallout: false, + }); + }); + + it('does not render the banner', () => { + expect(findUpgradeBanner().exists()).toBe(false); + }); + }); + + describe('given all features are available', () => { + beforeEach(() => { + createComponent({ + augmentedSecurityFeatures: securityFeaturesMock.map(makeAvailable(true)), + augmentedComplianceFeatures: complianceFeaturesMock.map(makeAvailable(true)), + }); + }); + + it('does not render the banner', () => { + expect(findUpgradeBanner().exists()).toBe(false); + }); + }); + }); + + describe('when given latestPipelinePath props', () => { + beforeEach(() => { + createComponent({ + augmentedSecurityFeatures: securityFeaturesMock, + augmentedComplianceFeatures: complianceFeaturesMock, + latestPipelinePath: 'test/path', + }); + }); + + it('should show latest pipeline info on the security tab with correct link when latestPipelinePath is defined', () => { + const latestPipelineInfoSecurity = findByTestId('latest-pipeline-info-security'); + + expect(latestPipelineInfoSecurity.exists()).toBe(true); + expect(latestPipelineInfoSecurity.text()).toMatchInterpolatedText( + i18n.securityTestingDescription, + ); + expect(latestPipelineInfoSecurity.find('a').attributes('href')).toBe('test/path'); + }); + + it('should show latest pipeline info on the compliance tab with correct link when latestPipelinePath is defined', () => { + const latestPipelineInfoCompliance = findByTestId('latest-pipeline-info-compliance'); + + expect(latestPipelineInfoCompliance.exists()).toBe(true); + expect(latestPipelineInfoCompliance.text()).toMatchInterpolatedText( + i18n.securityTestingDescription, + ); + expect(latestPipelineInfoCompliance.find('a').attributes('href')).toBe('test/path'); + }); + }); + + describe('given gitlabCiPresent & gitlabCiHistoryPath props', () => { + beforeEach(() => { + createComponent({ + augmentedSecurityFeatures: securityFeaturesMock, + augmentedComplianceFeatures: complianceFeaturesMock, + gitlabCiPresent: true, + gitlabCiHistoryPath: 'test/historyPath', + }); + }); + + it('should show configuration History Link', () => { + expect(findComplianceViewHistoryLink().exists()).toBe(true); + expect(findSecurityViewHistoryLink().exists()).toBe(true); + + expect(findComplianceViewHistoryLink().attributes('href')).toBe('test/historyPath'); + expect(findSecurityViewHistoryLink().attributes('href')).toBe('test/historyPath'); + }); + }); +}); diff --git a/spec/frontend/security_configuration/components/section_layout_spec.js b/spec/frontend/security_configuration/components/section_layout_spec.js new file mode 100644 index 00000000000..75da380bbb8 --- /dev/null +++ b/spec/frontend/security_configuration/components/section_layout_spec.js @@ -0,0 +1,49 @@ +import { mount } from '@vue/test-utils'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import SectionLayout from '~/security_configuration/components/section_layout.vue'; + +describe('Section Layout component', () => { + let wrapper; + + const createComponent = (propsData) => { + wrapper = extendedWrapper( + mount(SectionLayout, { + propsData, + scopedSlots: { + description: '<span>foo</span>', + features: '<span>bar</span>', + }, + }), + ); + }; + + const findHeading = () => wrapper.find('h2'); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('basic structure', () => { + beforeEach(() => { + createComponent({ heading: 'testheading' }); + }); + + const slots = { + description: 'foo', + features: 'bar', + }; + + it('should render heading when passed in as props', () => { + expect(findHeading().exists()).toBe(true); + expect(findHeading().text()).toBe('testheading'); + }); + + Object.keys(slots).forEach((slot) => { + it('renders the slots', () => { + const slotContent = slots[slot]; + createComponent({ heading: '' }); + expect(wrapper.text()).toContain(slotContent); + }); + }); + }); +}); diff --git a/spec/frontend/security_configuration/components/upgrade_banner_spec.js b/spec/frontend/security_configuration/components/upgrade_banner_spec.js new file mode 100644 index 00000000000..cf7945343af --- /dev/null +++ b/spec/frontend/security_configuration/components/upgrade_banner_spec.js @@ -0,0 +1,60 @@ +import { GlBanner } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import UpgradeBanner from '~/security_configuration/components/upgrade_banner.vue'; + +const upgradePath = '/upgrade'; + +describe('UpgradeBanner component', () => { + let wrapper; + let closeSpy; + + const createComponent = (propsData) => { + closeSpy = jest.fn(); + + wrapper = shallowMountExtended(UpgradeBanner, { + provide: { + upgradePath, + }, + propsData, + listeners: { + close: closeSpy, + }, + }); + }; + + const findGlBanner = () => wrapper.findComponent(GlBanner); + + beforeEach(() => { + createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('passes the expected props to GlBanner', () => { + expect(findGlBanner().props()).toMatchObject({ + title: UpgradeBanner.i18n.title, + buttonText: UpgradeBanner.i18n.buttonText, + buttonLink: upgradePath, + }); + }); + + it('renders the list of benefits', () => { + const wrapperText = wrapper.text(); + + expect(wrapperText).toContain('GitLab Ultimate checks your application'); + expect(wrapperText).toContain('statistics in the merge request'); + expect(wrapperText).toContain('statistics across projects'); + expect(wrapperText).toContain('Runtime security metrics'); + expect(wrapperText).toContain('risk analysis and remediation'); + }); + + it(`re-emits GlBanner's close event`, () => { + expect(closeSpy).not.toHaveBeenCalled(); + + wrapper.findComponent(GlBanner).vm.$emit('close'); + + expect(closeSpy).toHaveBeenCalledTimes(1); + }); +}); diff --git a/spec/frontend/security_configuration/utils_spec.js b/spec/frontend/security_configuration/utils_spec.js new file mode 100644 index 00000000000..6ad167cadda --- /dev/null +++ b/spec/frontend/security_configuration/utils_spec.js @@ -0,0 +1,81 @@ +import { augmentFeatures } from '~/security_configuration/utils'; + +const mockSecurityFeatures = [ + { + name: 'SAST', + type: 'SAST', + }, +]; + +const mockComplianceFeatures = [ + { + name: 'LICENSE_COMPLIANCE', + type: 'LICENSE_COMPLIANCE', + }, +]; + +const mockFeaturesWithSecondary = [ + { + name: 'DAST', + type: 'DAST', + secondary: { + type: 'DAST PROFILES', + name: 'DAST PROFILES', + }, + }, +]; + +const mockInvalidCustomFeature = [ + { + foo: 'bar', + }, +]; + +const mockValidCustomFeature = [ + { + name: 'SAST', + type: 'SAST', + customfield: 'customvalue', + }, +]; + +const expectedOutputDefault = { + augmentedSecurityFeatures: mockSecurityFeatures, + augmentedComplianceFeatures: mockComplianceFeatures, +}; + +const expectedOutputSecondary = { + augmentedSecurityFeatures: mockSecurityFeatures, + augmentedComplianceFeatures: mockFeaturesWithSecondary, +}; + +const expectedOutputCustomFeature = { + augmentedSecurityFeatures: mockValidCustomFeature, + augmentedComplianceFeatures: mockComplianceFeatures, +}; + +describe('returns an object with augmentedSecurityFeatures and augmentedComplianceFeatures when', () => { + it('given an empty array', () => { + expect(augmentFeatures(mockSecurityFeatures, mockComplianceFeatures, [])).toEqual( + expectedOutputDefault, + ); + }); + + it('given an invalid populated array', () => { + expect( + augmentFeatures(mockSecurityFeatures, mockComplianceFeatures, mockInvalidCustomFeature), + ).toEqual(expectedOutputDefault); + }); + + it('features have secondary key', () => { + expect(augmentFeatures(mockSecurityFeatures, mockFeaturesWithSecondary, [])).toEqual( + expectedOutputSecondary, + ); + }); + + it('given a valid populated array', () => { + expect( + augmentFeatures(mockSecurityFeatures, mockComplianceFeatures, mockValidCustomFeature), + ).toEqual(expectedOutputCustomFeature); + }); +}); |