gitlab-org--gitlab-foss/spec/frontend/security_configuration/components/app_spec.js

453 lines
16 KiB
JavaScript

import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import { GlTab, GlTabs, GlLink } from '@gitlab/ui';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser';
import stubChildren from 'helpers/stub_children';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import SecurityConfigurationApp, { i18n } from '~/security_configuration/components/app.vue';
import AutoDevopsAlert from '~/security_configuration/components/auto_dev_ops_alert.vue';
import AutoDevopsEnabledAlert from '~/security_configuration/components/auto_dev_ops_enabled_alert.vue';
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,
AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY,
} from '~/security_configuration/components/constants';
import FeatureCard from '~/security_configuration/components/feature_card.vue';
import TrainingProviderList from '~/security_configuration/components/training_provider_list.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';
const autoDevopsHelpPagePath = '/autoDevopsHelpPagePath';
const autoDevopsPath = '/autoDevopsPath';
const gitlabCiHistoryPath = 'test/historyPath';
const projectFullPath = 'namespace/project';
const vulnerabilityTrainingDocsPath = 'user/application_security/vulnerabilities/index';
useLocalStorageSpy();
Vue.use(VueApollo);
describe('App component', () => {
let wrapper;
let userCalloutDismissSpy;
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,
},
];
const createComponent = ({ shouldShowCallout = true, ...propsData } = {}) => {
userCalloutDismissSpy = jest.fn();
wrapper = mountExtended(SecurityConfigurationApp, {
propsData: {
augmentedSecurityFeatures: securityFeaturesMock,
augmentedComplianceFeatures: complianceFeaturesMock,
securityTrainingEnabled: true,
...propsData,
},
provide: {
upgradePath,
autoDevopsHelpPagePath,
autoDevopsPath,
projectFullPath,
vulnerabilityTrainingDocsPath,
},
stubs: {
...stubChildren(SecurityConfigurationApp),
GlLink: false,
GlSprintf: false,
LocalStorageSync: false,
SectionLayout: false,
UserCalloutDismisser: makeMockUserCalloutDismisser({
dismiss: userCalloutDismissSpy,
shouldShowCallout,
}),
},
});
};
const findMainHeading = () => wrapper.find('h1');
const findTab = () => wrapper.findComponent(GlTab);
const findTabs = () => wrapper.findAllComponents(GlTab);
const findGlTabs = () => wrapper.findComponent(GlTabs);
const findByTestId = (id) => wrapper.findByTestId(id);
const findFeatureCards = () => wrapper.findAllComponents(FeatureCard);
const findTrainingProviderList = () => wrapper.findComponent(TrainingProviderList);
const findManageViaMRErrorAlert = () => wrapper.findByTestId('manage-via-mr-error-alert');
const findLink = ({ href, text, container = wrapper }) => {
const selector = `a[href="${href}"]`;
const link = container.find(selector);
if (link.exists() && link.text() === text) {
return link;
}
return wrapper.find(`${selector} does not exist`);
};
const findSecurityViewHistoryLink = () =>
findLink({
href: gitlabCiHistoryPath,
text: i18n.configurationHistory,
container: findByTestId('security-testing-tab'),
});
const findComplianceViewHistoryLink = () =>
findLink({
href: gitlabCiHistoryPath,
text: i18n.configurationHistory,
container: findByTestId('compliance-testing-tab'),
});
const findUpgradeBanner = () => wrapper.findComponent(UpgradeBanner);
const findAutoDevopsAlert = () => wrapper.findComponent(AutoDevopsAlert);
const findAutoDevopsEnabledAlert = () => wrapper.findComponent(AutoDevopsEnabledAlert);
const findVulnerabilityManagementTab = () => wrapper.findByTestId('vulnerability-management-tab');
afterEach(() => {
wrapper.destroy();
});
describe('basic structure', () => {
beforeEach(() => {
createComponent();
});
it('renders main-heading with correct text', () => {
const mainHeading = findMainHeading();
expect(mainHeading.exists()).toBe(true);
expect(mainHeading.text()).toContain('Security Configuration');
});
describe('tabs', () => {
const expectedTabs = ['security-testing', 'compliance-testing', 'vulnerability-management'];
it('renders GlTab Component', () => {
expect(findTab().exists()).toBe(true);
});
it('passes the `sync-active-tab-with-query-params` prop', () => {
expect(findGlTabs().props('syncActiveTabWithQueryParams')).toBe(true);
});
it('lazy loads each tab', () => {
expect(findGlTabs().attributes('lazy')).not.toBe(undefined);
});
it('renders correct amount of tabs', () => {
expect(findTabs()).toHaveLength(expectedTabs.length);
});
it.each(expectedTabs)('renders the %s tab', (tabName) => {
expect(findByTestId(`${tabName}-tab`).exists()).toBe(true);
});
it.each(expectedTabs)('has the %s query-param-value', (tabName) => {
expect(findByTestId(`${tabName}-tab`).props('queryParamValue')).toBe(tabName);
});
});
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('renders a basic description', () => {
expect(wrapper.text()).toContain(i18n.description);
});
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('Manage via MR Error Alert', () => {
beforeEach(() => {
createComponent();
});
describe('on initial load', () => {
it('should not show Manage via MR Error Alert', () => {
expect(findManageViaMRErrorAlert().exists()).toBe(false);
});
});
describe('when error occurs', () => {
it('should show Alert with error Message', async () => {
expect(findManageViaMRErrorAlert().exists()).toBe(false);
findFeatureCards().at(1).vm.$emit('error', 'There was a manage via MR error');
await nextTick();
expect(findManageViaMRErrorAlert().exists()).toBe(true);
expect(findManageViaMRErrorAlert().text()).toEqual('There was a manage via MR error');
});
it('should hide Alert when it is dismissed', async () => {
findFeatureCards().at(1).vm.$emit('error', 'There was a manage via MR error');
await nextTick();
expect(findManageViaMRErrorAlert().exists()).toBe(true);
findManageViaMRErrorAlert().vm.$emit('dismiss');
await nextTick();
expect(findManageViaMRErrorAlert().exists()).toBe(false);
});
});
});
describe('Auto DevOps hint alert', () => {
describe('given the right props', () => {
beforeEach(() => {
createComponent({
autoDevopsEnabled: false,
gitlabCiPresent: false,
canEnableAutoDevops: true,
});
});
it('should show AutoDevopsAlert', () => {
expect(findAutoDevopsAlert().exists()).toBe(true);
});
it('calls the dismiss callback when closing the AutoDevopsAlert', () => {
expect(userCalloutDismissSpy).not.toHaveBeenCalled();
findAutoDevopsAlert().vm.$emit('dismiss');
expect(userCalloutDismissSpy).toHaveBeenCalledTimes(1);
});
});
describe('given the wrong props', () => {
beforeEach(() => {
createComponent();
});
it('should not show AutoDevopsAlert', () => {
expect(findAutoDevopsAlert().exists()).toBe(false);
});
});
});
describe('Auto DevOps enabled alert', () => {
describe.each`
context | autoDevopsEnabled | localStorageValue | shouldRender
${'enabled'} | ${true} | ${null} | ${true}
${'enabled, alert dismissed on other project'} | ${true} | ${['foo/bar']} | ${true}
${'enabled, alert dismissed on this project'} | ${true} | ${[projectFullPath]} | ${false}
${'not enabled'} | ${false} | ${null} | ${false}
`('given Auto DevOps is $context', ({ autoDevopsEnabled, localStorageValue, shouldRender }) => {
beforeEach(() => {
if (localStorageValue !== null) {
window.localStorage.setItem(
AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY,
JSON.stringify(localStorageValue),
);
}
createComponent({
autoDevopsEnabled,
});
});
it(shouldRender ? 'renders' : 'does not render', () => {
expect(findAutoDevopsEnabledAlert().exists()).toBe(shouldRender);
});
});
describe('dismissing', () => {
describe.each`
dismissedProjects | expectedWrittenValue
${null} | ${[projectFullPath]}
${[]} | ${[projectFullPath]}
${['foo/bar']} | ${['foo/bar', projectFullPath]}
${[projectFullPath]} | ${[projectFullPath]}
`(
'given dismissed projects $dismissedProjects',
({ dismissedProjects, expectedWrittenValue }) => {
beforeEach(() => {
if (dismissedProjects !== null) {
window.localStorage.setItem(
AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY,
JSON.stringify(dismissedProjects),
);
}
createComponent({
augmentedSecurityFeatures: securityFeaturesMock,
augmentedComplianceFeatures: complianceFeaturesMock,
autoDevopsEnabled: true,
});
findAutoDevopsEnabledAlert().vm.$emit('dismiss');
});
it('adds current project to localStorage value', () => {
expect(window.localStorage.setItem).toHaveBeenLastCalledWith(
AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY,
JSON.stringify(expectedWrittenValue),
);
});
it('hides the alert', () => {
expect(findAutoDevopsEnabledAlert().exists()).toBe(false);
});
},
);
});
});
describe('upgrade banner', () => {
const makeAvailable = (available) => (feature) => ({ ...feature, available });
describe('given at least one unavailable feature', () => {
beforeEach(() => {
createComponent({
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({
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({
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.text()).toMatchInterpolatedText(
i18n.latestPipelineDescription,
);
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.text()).toMatchInterpolatedText(
i18n.latestPipelineDescription,
);
expect(latestPipelineInfoCompliance.find('a').attributes('href')).toBe('test/path');
});
});
describe('given gitlabCiPresent & gitlabCiHistoryPath props', () => {
beforeEach(() => {
createComponent({
gitlabCiPresent: true,
gitlabCiHistoryPath,
});
});
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');
});
});
describe('Vulnerability management', () => {
const props = { securityTrainingEnabled: true };
beforeEach(async () => {
createComponent({
...props,
});
});
it('shows the tab', () => {
expect(findVulnerabilityManagementTab().exists()).toBe(true);
});
it('renders TrainingProviderList component', () => {
expect(findTrainingProviderList().props()).toMatchObject(props);
});
it('renders security training description', () => {
expect(findVulnerabilityManagementTab().text()).toContain(i18n.securityTrainingDescription);
});
it('renders link to help docs', () => {
const trainingLink = findVulnerabilityManagementTab().findComponent(GlLink);
expect(trainingLink.text()).toBe('Learn more about vulnerability training');
expect(trainingLink.attributes('href')).toBe(vulnerabilityTrainingDocsPath);
});
});
});