Commit 5bc6ae0d authored by Zamir Martins's avatar Zamir Martins Committed by Vitaly Slobodin

Add scan result policy into policy editor

EE: true
Changelog: changed
parent 2f33a8cb
......@@ -22,3 +22,17 @@ export const DELETE_MODAL_CONFIG = {
text: __('Cancel'),
},
};
export const DEFAULT_MR_TITLE = s__('SecurityOrchestration|Update scan policies');
export const SECURITY_POLICY_ACTIONS = Object.freeze({
APPEND: 'APPEND',
REMOVE: 'REMOVE',
REPLACE: 'REPLACE',
});
export const GRAPHQL_ERROR_MESSAGE = s__(
'SecurityOrchestration|There was a problem creating the new security policy',
);
export const NO_RULE_MESSAGE = s__('SecurityOrchestration|No rules defined - policy will not run.');
<script>
import { GlAlert, GlFormGroup, GlFormSelect } from '@gitlab/ui';
import { s__ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { POLICY_TYPE_COMPONENT_OPTIONS } from '../constants';
import EnvironmentPicker from '../environment_picker.vue';
import NetworkPolicyEditor from './network_policy/network_policy_editor.vue';
import ScanExecutionPolicyEditor from './scan_execution_policy/scan_execution_policy_editor.vue';
import ScanResultPolicyEditor from './scan_result_policy/scan_result_policy_editor.vue';
export default {
components: {
......@@ -14,7 +16,9 @@ export default {
EnvironmentPicker,
NetworkPolicyEditor,
ScanExecutionPolicyEditor,
ScanResultPolicyEditor,
},
mixins: [glFeatureFlagMixin()],
inject: ['policyType'],
props: {
assignedPolicyProject: {
......@@ -38,13 +42,23 @@ export default {
return this.isEditing ? this.policyType : this.newPolicyType;
},
isEditing() {
if (!this.existingPolicy) {
return false;
}
return Boolean(
this.existingPolicy?.creation_timestamp ||
this.existingPolicy?.type === POLICY_TYPE_COMPONENT_OPTIONS.scanExecution.urlParameter,
this.existingPolicy.creation_timestamp ||
[
POLICY_TYPE_COMPONENT_OPTIONS.scanExecution.urlParameter,
POLICY_TYPE_COMPONENT_OPTIONS.scanResult?.urlParameter,
].includes(this.existingPolicy.type),
);
},
policyTypes() {
return Object.values(POLICY_TYPE_COMPONENT_OPTIONS);
const types = Object.values(POLICY_TYPE_COMPONENT_OPTIONS);
return this.isScanResultPolicyEnabled
? types
: types.filter((type) => type.value !== POLICY_TYPE_COMPONENT_OPTIONS.scanResult?.value);
},
policyOptions() {
return (
......@@ -58,6 +72,9 @@ export default {
shouldAllowPolicyTypeSelection() {
return !this.existingPolicy;
},
isScanResultPolicyEnabled() {
return this.glFeatures.scanResultPolicy;
},
},
methods: {
setError(error) {
......
import { s__ } from '~/locale';
export const DEFAULT_MR_TITLE = s__('SecurityOrchestration|Update scan execution policies');
export const GRAPHQL_ERROR_MESSAGE = s__(
'SecurityOrchestration|There was a problem creating the new security policy',
);
export const NO_RULE_MESSAGE = s__('SecurityOrchestration|No rules defined - policy will not run.');
export const SECURITY_POLICY_ACTIONS = {
APPEND: 'APPEND',
REMOVE: 'REMOVE',
REPLACE: 'REPLACE',
};
import cronstrue from 'cronstrue/i18n';
import { convertToTitleCase, humanize } from '~/lib/utils/text_utility';
import { getPreferredLocales, sprintf, s__, n__ } from '~/locale';
import { NO_RULE_MESSAGE } from './constants';
import { NO_RULE_MESSAGE } from '../../constants';
const getActionText = (scanType) =>
sprintf(s__('SecurityOrchestration|Executes a %{scanType} scan'), {
......
export { fromYaml } from './from_yaml';
export { toYaml } from './to_yaml';
export * from './constants';
export * from './humanize';
export * from './utils';
export const DEFAULT_SCAN_EXECUTION_POLICY = `type: scan_execution_policy
name: ''
......
......@@ -2,17 +2,15 @@
import { GlEmptyState } from '@gitlab/ui';
import { joinPaths, visitUrl } from '~/lib/utils/url_utility';
import { __, s__ } from '~/locale';
import { EDITOR_MODES, EDITOR_MODE_YAML } from '../constants';
import PolicyEditorLayout from '../policy_editor_layout.vue';
import {
assignSecurityPolicyProject,
DEFAULT_SCAN_EXECUTION_POLICY,
fromYaml,
GRAPHQL_ERROR_MESSAGE,
modifyPolicy,
EDITOR_MODES,
EDITOR_MODE_YAML,
SECURITY_POLICY_ACTIONS,
toYaml,
} from './lib';
GRAPHQL_ERROR_MESSAGE,
} from '../constants';
import PolicyEditorLayout from '../policy_editor_layout.vue';
import { assignSecurityPolicyProject, modifyPolicy } from '../utils';
import { DEFAULT_SCAN_EXECUTION_POLICY, fromYaml, toYaml } from './lib';
export default {
SECURITY_POLICY_ACTIONS,
......
import { s__ } from '~/locale';
export const NO_RULE_MESSAGE = s__('SecurityOrchestration|No rules defined - policy will not run.');
import { sprintf, s__, n__ } from '~/locale';
import { NO_RULE_MESSAGE } from './constants';
import { NO_RULE_MESSAGE } from '../../constants';
/**
* Simple logic for indefinite articles which does not include the exceptions
......
export { fromYaml } from './from_yaml';
export { toYaml } from './to_yaml';
export * from './humanize';
export * from './constants';
export const DEFAULT_SCAN_RESULT_POLICY = `type: scan_result_policy
name: ''
description: ''
enabled: false
rules:
- type: scan_finding
branches:
- main
scanners:
- container_scanning
vulnerabilities_allowed: 0
severity_levels:
- critical
vulnerability_states:
- newly_added
actions:
- type: require_approval
approvals_required: 1
user_approvers:
- security_user
`;
import { safeDump } from 'js-yaml';
/*
Return yaml representation of a policy.
*/
export const toYaml = (policy) => {
return safeDump(policy);
};
<script>
import { GlEmptyState } from '@gitlab/ui';
import { joinPaths, visitUrl } from '~/lib/utils/url_utility';
import { __, s__ } from '~/locale';
import {
EDITOR_MODES,
EDITOR_MODE_YAML,
SECURITY_POLICY_ACTIONS,
GRAPHQL_ERROR_MESSAGE,
} from '../constants';
import PolicyEditorLayout from '../policy_editor_layout.vue';
import { assignSecurityPolicyProject, modifyPolicy } from '../utils';
import { DEFAULT_SCAN_RESULT_POLICY, fromYaml, toYaml } from './lib';
export default {
SECURITY_POLICY_ACTIONS,
DEFAULT_EDITOR_MODE: EDITOR_MODE_YAML,
EDITOR_MODES: [EDITOR_MODES[1]],
i18n: {
createMergeRequest: __('Create via merge request'),
notOwnerButtonText: __('Learn more'),
notOwnerDescription: s__(
'SecurityOrchestration|Scan result policies can only be created by project owners.',
),
},
components: {
GlEmptyState,
PolicyEditorLayout,
},
inject: [
'disableScanExecutionUpdate',
'policyEditorEmptyStateSvgPath',
'projectId',
'projectPath',
'scanExecutionDocumentationPath',
],
props: {
assignedPolicyProject: {
type: Object,
required: true,
},
existingPolicy: {
type: Object,
required: false,
default: null,
},
isEditing: {
type: Boolean,
required: false,
default: false,
},
},
data() {
const yamlEditorValue = this.existingPolicy
? toYaml(this.existingPolicy)
: DEFAULT_SCAN_RESULT_POLICY;
return {
error: '',
isCreatingMR: false,
isRemovingPolicy: false,
newlyCreatedPolicyProject: null,
policy: fromYaml(yamlEditorValue),
yamlEditorValue,
};
},
computed: {
originalName() {
return this.existingPolicy?.name;
},
policyActionName() {
return this.isEditing
? this.$options.SECURITY_POLICY_ACTIONS.REPLACE
: this.$options.SECURITY_POLICY_ACTIONS.APPEND;
},
},
methods: {
handleError(error) {
if (error.message.toLowerCase().includes('graphql')) {
this.$emit('error', GRAPHQL_ERROR_MESSAGE);
} else {
this.$emit('error', error.message);
}
},
async getSecurityPolicyProject() {
if (!this.newlyCreatedPolicyProject && !this.assignedPolicyProject.fullPath) {
this.newlyCreatedPolicyProject = await assignSecurityPolicyProject(this.projectPath);
}
return this.newlyCreatedPolicyProject || this.assignedPolicyProject;
},
async handleModifyPolicy(act) {
const action = act || this.policyActionName;
this.$emit('error', '');
this.setLoadingFlag(action, true);
try {
const assignedPolicyProject = await this.getSecurityPolicyProject();
const mergeRequest = await modifyPolicy({
action,
assignedPolicyProject,
name: this.originalName || fromYaml(this.yamlEditorValue)?.name,
projectPath: this.projectPath,
yamlEditorValue: this.yamlEditorValue,
});
this.redirectToMergeRequest({ mergeRequest, assignedPolicyProject });
} catch (e) {
this.handleError(e);
this.setLoadingFlag(action, false);
}
},
setLoadingFlag(action, val) {
if (action === SECURITY_POLICY_ACTIONS.REMOVE) {
this.isRemovingPolicy = val;
} else {
this.isCreatingMR = val;
}
},
redirectToMergeRequest({ mergeRequest, assignedPolicyProject }) {
visitUrl(
joinPaths(
gon.relative_url_root || '/',
assignedPolicyProject.fullPath,
'/-/merge_requests',
mergeRequest.id,
),
);
},
updateYaml(manifest) {
this.yamlEditorValue = manifest;
},
},
};
</script>
<template>
<policy-editor-layout
v-if="!disableScanExecutionUpdate"
:custom-save-button-text="$options.i18n.createMergeRequest"
:default-editor-mode="$options.DEFAULT_EDITOR_MODE"
:editor-modes="$options.EDITOR_MODES"
:is-editing="isEditing"
:is-removing-policy="isRemovingPolicy"
:is-updating-policy="isCreatingMR"
:policy-name="policy.name"
:yaml-editor-value="yamlEditorValue"
@remove-policy="handleModifyPolicy($options.SECURITY_POLICY_ACTIONS.REMOVE)"
@save-policy="handleModifyPolicy()"
@update-yaml="updateYaml"
/>
<gl-empty-state
v-else
:description="$options.i18n.notOwnerDescription"
:primary-button-link="scanExecutionDocumentationPath"
:primary-button-text="$options.i18n.notOwnerButtonText"
:svg-path="policyEditorEmptyStateSvgPath"
title=""
/>
</template>
......@@ -5,8 +5,13 @@ import EnvironmentPicker from 'ee/threat_monitoring/components/environment_picke
import NetworkPolicyEditor from 'ee/threat_monitoring/components/policy_editor/network_policy/network_policy_editor.vue';
import PolicyEditor from 'ee/threat_monitoring/components/policy_editor/policy_editor.vue';
import ScanExecutionPolicyEditor from 'ee/threat_monitoring/components/policy_editor/scan_execution_policy/scan_execution_policy_editor.vue';
import ScanResultPolicyEditor from 'ee/threat_monitoring/components/policy_editor/scan_result_policy/scan_result_policy_editor.vue';
import { DEFAULT_ASSIGNED_POLICY_PROJECT } from 'ee/threat_monitoring/constants';
import { mockDastScanExecutionObject, mockL3Manifest } from '../../mocks/mock_data';
import {
mockDastScanExecutionObject,
mockL3Manifest,
mockScanResultObject,
} from '../../mocks/mock_data';
describe('PolicyEditor component', () => {
let wrapper;
......@@ -16,6 +21,7 @@ describe('PolicyEditor component', () => {
const findFormSelect = () => wrapper.findComponent(GlFormSelect);
const findNeworkPolicyEditor = () => wrapper.findComponent(NetworkPolicyEditor);
const findScanExecutionPolicyEditor = () => wrapper.findComponent(ScanExecutionPolicyEditor);
const findScanResultPolicyEditor = () => wrapper.findComponent(ScanResultPolicyEditor);
const factory = ({ propsData = {}, provide = {} } = {}) => {
wrapper = shallowMount(PolicyEditor, {
......@@ -25,6 +31,7 @@ describe('PolicyEditor component', () => {
},
provide: {
policyType: undefined,
glFeatures: { scanResultPolicy: true },
...provide,
},
stubs: { GlFormSelect },
......@@ -69,6 +76,7 @@ describe('PolicyEditor component', () => {
policyType | option | findComponent
${'container'} | ${POLICY_TYPE_COMPONENT_OPTIONS.container} | ${findNeworkPolicyEditor}
${'scanExecution'} | ${POLICY_TYPE_COMPONENT_OPTIONS.scanExecution} | ${findScanExecutionPolicyEditor}
${'scanResult'} | ${POLICY_TYPE_COMPONENT_OPTIONS.scanResult} | ${findScanResultPolicyEditor}
`(
'renders the policy editor of type $policyType when selected',
async ({ findComponent, option, policyType }) => {
......@@ -81,6 +89,26 @@ describe('PolicyEditor component', () => {
expect(component.props('isEditing')).toBe(false);
},
);
describe('with scan_result_policy feature flag disabled', () => {
beforeEach(() => {
factory({ provide: { glFeatures: { scanResultPolicy: false } } });
const formSelect = findFormSelect();
formSelect.vm.$emit('change', POLICY_TYPE_COMPONENT_OPTIONS.scanResult.value);
wrapper.vm.$nextTick();
});
it('does not render scan result policy', () => {
const component = findScanResultPolicyEditor();
expect(component.exists()).toBe(false);
});
it('renders network policy with isEditing set to false', () => {
const component = findNeworkPolicyEditor();
expect(component.exists()).toBe(true);
expect(component.props('isEditing')).toBe(false);
});
});
});
describe('when an existing policy is present', () => {
......@@ -88,10 +116,14 @@ describe('PolicyEditor component', () => {
policyType | option | existingPolicy | findComponent
${'container_policy'} | ${POLICY_TYPE_COMPONENT_OPTIONS.container} | ${{ manifest: mockL3Manifest, creation_timestamp: '2020-04-14T00:08:30Z' }} | ${findNeworkPolicyEditor}
${'scan_execution_policy'} | ${POLICY_TYPE_COMPONENT_OPTIONS.scanExecution} | ${mockDastScanExecutionObject} | ${findScanExecutionPolicyEditor}
${'scan_result_policy'} | ${POLICY_TYPE_COMPONENT_OPTIONS.scanResult} | ${mockScanResultObject} | ${findScanResultPolicyEditor}
`(
'renders the disabled form select for existing policy of type $policyType',
async ({ existingPolicy, findComponent, option, policyType }) => {
factory({ propsData: { existingPolicy }, provide: { policyType } });
factory({
propsData: { existingPolicy },
provide: { policyType, glFeatures: { scanResultPolicy: true } },
});
await wrapper.vm.$nextTick();
const formSelect = findFormSelect();
expect(formSelect.exists()).toBe(true);
......@@ -102,5 +134,31 @@ describe('PolicyEditor component', () => {
expect(component.props('isEditing')).toBe(true);
},
);
describe('with scan_result_policy feature flag disabled', () => {
beforeEach(() => {
factory({
propsData: { existingPolicy: mockScanResultObject },
provide: {
policyType: POLICY_TYPE_COMPONENT_OPTIONS.scanResult.urlParameter,
glFeatures: { scanResultPolicy: false },
},
});
});
it('does not display the scan result as one of the dropdown options', () => {
const formSelect = findFormSelect();
expect(formSelect.vm.$attrs.options).toMatchObject([
POLICY_TYPE_COMPONENT_OPTIONS.container,
POLICY_TYPE_COMPONENT_OPTIONS.scanExecution,
]);
});
it('renders network policy with isEditing set to true', () => {
const component = findNeworkPolicyEditor();
expect(component.exists()).toBe(true);
expect(component.props('isEditing')).toBe(true);
});
});
});
});
import {
humanizeActions,
humanizeRules,
NO_RULE_MESSAGE,
} from 'ee/threat_monitoring/components/policy_editor/scan_execution_policy/lib';
import { NO_RULE_MESSAGE } from 'ee/threat_monitoring/components/policy_editor/constants';
jest.mock('~/locale', () => ({
getPreferredLocales: jest.fn().mockReturnValue(['en']),
sprintf: jest.requireActual('~/locale').sprintf,
s__: jest.requireActual('~/locale').s__, // eslint-disable-line no-underscore-dangle
n__: jest.requireActual('~/locale').n__, // eslint-disable-line no-underscore-dangle
__: jest.requireActual('~/locale').__,
}));
const mockActions = [
......
import {
assignSecurityPolicyProject,
modifyPolicy,
} from 'ee/threat_monitoring/components/policy_editor/scan_execution_policy/lib/utils';
} from 'ee/threat_monitoring/components/policy_editor/utils';
import { DEFAULT_ASSIGNED_POLICY_PROJECT } from 'ee/threat_monitoring/constants';
import createPolicyProject from 'ee/threat_monitoring/graphql/mutations/create_policy_project.mutation.graphql';
import createScanExecutionPolicy from 'ee/threat_monitoring/graphql/mutations/create_scan_execution_policy.mutation.graphql';
......
......@@ -5,8 +5,6 @@ import PolicyEditorLayout from 'ee/threat_monitoring/components/policy_editor/po
import {
DEFAULT_SCAN_EXECUTION_POLICY,
fromYaml,
modifyPolicy,
SECURITY_POLICY_ACTIONS,
} from 'ee/threat_monitoring/components/policy_editor/scan_execution_policy/lib';
import ScanExecutionPolicyEditor from 'ee/threat_monitoring/components/policy_editor/scan_execution_policy/scan_execution_policy_editor.vue';
import { DEFAULT_ASSIGNED_POLICY_PROJECT } from 'ee/threat_monitoring/constants';
......@@ -16,6 +14,9 @@ import {
} from 'ee_jest/threat_monitoring/mocks/mock_data';
import { visitUrl } from '~/lib/utils/url_utility';
import { modifyPolicy } from 'ee/threat_monitoring/components/policy_editor/utils';
import { SECURITY_POLICY_ACTIONS } from 'ee/threat_monitoring/components/policy_editor/constants';
jest.mock('~/lib/utils/url_utility', () => ({
joinPaths: jest.requireActual('~/lib/utils/url_utility').joinPaths,
visitUrl: jest.fn().mockName('visitUrlMock'),
......@@ -27,10 +28,6 @@ const newlyCreatedPolicyProject = {
};
jest.mock('ee/threat_monitoring/components/policy_editor/scan_execution_policy/lib', () => ({
assignSecurityPolicyProject: jest.fn().mockResolvedValue({
branch: 'main',
fullPath: 'path/to/new-project',
}),
DEFAULT_SCAN_EXECUTION_POLICY: jest.requireActual(
'ee/threat_monitoring/components/policy_editor/scan_execution_policy/lib',
).DEFAULT_SCAN_EXECUTION_POLICY,
......@@ -41,8 +38,15 @@ jest.mock('ee/threat_monitoring/components/policy_editor/scan_execution_policy/l
'ee/threat_monitoring/components/policy_editor/scan_execution_policy/lib',
).toYaml,
SECURITY_POLICY_ACTIONS: jest.requireActual(
'ee/threat_monitoring/components/policy_editor/scan_execution_policy/lib',
'ee/threat_monitoring/components/policy_editor/constants',
).SECURITY_POLICY_ACTIONS,
}));
jest.mock('ee/threat_monitoring/components/policy_editor/utils', () => ({
assignSecurityPolicyProject: jest.fn().mockResolvedValue({
branch: 'main',
fullPath: 'path/to/new-project',
}),
modifyPolicy: jest.fn().mockResolvedValue({ id: '2' }),
}));
......
import {
humanizeRules,
humanizeAction,
NO_RULE_MESSAGE,
} from 'ee/threat_monitoring/components/policy_editor/scan_result_policy/lib';
import { NO_RULE_MESSAGE } from 'ee/threat_monitoring/components/policy_editor/constants';
jest.mock('~/locale', () => ({
getPreferredLocales: jest.fn().mockReturnValue(['en']),
sprintf: jest.requireActual('~/locale').sprintf,
s__: jest.requireActual('~/locale').s__, // eslint-disable-line no-underscore-dangle
n__: jest.requireActual('~/locale').n__, // eslint-disable-line no-underscore-dangle
__: jest.requireActual('~/locale').__,
}));
const mockActions = [
......
import { shallowMount } from '@vue/test-utils';
import { GlEmptyState } from '@gitlab/ui';
import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import PolicyEditorLayout from 'ee/threat_monitoring/components/policy_editor/policy_editor_layout.vue';
import {
DEFAULT_SCAN_RESULT_POLICY,
fromYaml,
} from 'ee/threat_monitoring/components/policy_editor/scan_result_policy/lib';
import ScanResultPolicyEditor from 'ee/threat_monitoring/components/policy_editor/scan_result_policy/scan_result_policy_editor.vue';
import { DEFAULT_ASSIGNED_POLICY_PROJECT } from 'ee/threat_monitoring/constants';
import {
mockScanResultManifest,
mockScanResultObject,
} from 'ee_jest/threat_monitoring/mocks/mock_data';
import { visitUrl } from '~/lib/utils/url_utility';
import { modifyPolicy } from 'ee/threat_monitoring/components/policy_editor/utils';
import { SECURITY_POLICY_ACTIONS } from 'ee/threat_monitoring/components/policy_editor/constants';
jest.mock('~/lib/utils/url_utility', () => ({
joinPaths: jest.requireActual('~/lib/utils/url_utility').joinPaths,
visitUrl: jest.fn().mockName('visitUrlMock'),
}));
const newlyCreatedPolicyProject = {
branch: 'main',
fullPath: 'path/to/new-project',
};
jest.mock('ee/threat_monitoring/components/policy_editor/utils', () => ({
assignSecurityPolicyProject: jest.fn().mockResolvedValue({
branch: 'main',
fullPath: 'path/to/new-project',
}),
modifyPolicy: jest.fn().mockResolvedValue({ id: '2' }),
}));
describe('ScanResultPolicyEditor', () => {
let wrapper;
const defaultProjectPath = 'path/to/project';
const policyEditorEmptyStateSvgPath = 'path/to/svg';
const scanExecutionDocumentationPath = 'path/to/docs';
const assignedPolicyProject = {
branch: 'main',
fullPath: 'path/to/existing-project',
};
const factory = ({ propsData = {}, provide = {} } = {}) => {
wrapper = shallowMount(ScanResultPolicyEditor, {
propsData: {
assignedPolicyProject: DEFAULT_ASSIGNED_POLICY_PROJECT,
...propsData,
},
provide: {
disableScanExecutionUpdate: false,
policyEditorEmptyStateSvgPath,
projectId: 1,
projectPath: defaultProjectPath,
scanExecutionDocumentationPath,
...provide,
},
});
};
const factoryWithExistingPolicy = () => {
return factory({
propsData: {
assignedPolicyProject,
existingPolicy: mockScanResultObject,
isEditing: true,
},
});
};
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
const findPolicyEditorLayout = () => wrapper.findComponent(PolicyEditorLayout);
afterEach(() => {
wrapper.destroy();
});
describe('default', () => {
it('updates the policy yaml when "update-yaml" is emitted', async () => {
factory();
await nextTick();
const newManifest = 'new yaml!';
expect(findPolicyEditorLayout().attributes('yamleditorvalue')).toBe(
DEFAULT_SCAN_RESULT_POLICY,
);
await findPolicyEditorLayout().vm.$emit('update-yaml', newManifest);
expect(findPolicyEditorLayout().attributes('yamleditorvalue')).toBe(newManifest);
});
it.each`
status | action | event | factoryFn | yamlEditorValue | currentlyAssignedPolicyProject
${'to save a new policy'} | ${SECURITY_POLICY_ACTIONS.APPEND} | ${'save-policy'} | ${factory} | ${DEFAULT_SCAN_RESULT_POLICY} | ${newlyCreatedPolicyProject}
${'to update an existing policy'} | ${SECURITY_POLICY_ACTIONS.REPLACE} | ${'save-policy'} | ${factoryWithExistingPolicy} | ${mockScanResultManifest} | ${assignedPolicyProject}
${'to delete an existing policy'} | ${SECURITY_POLICY_ACTIONS.REMOVE} | ${'remove-policy'} | ${factoryWithExistingPolicy} | ${mockScanResultManifest} | ${assignedPolicyProject}
`(
'navigates to the new merge request when "modifyPolicy" is emitted $status',
async ({ action, event, factoryFn, yamlEditorValue, currentlyAssignedPolicyProject }) => {
factoryFn();
await nextTick();
findPolicyEditorLayout().vm.$emit(event);
await waitForPromises();
expect(modifyPolicy).toHaveBeenCalledWith({
action,
assignedPolicyProject: currentlyAssignedPolicyProject,
name:
action === SECURITY_POLICY_ACTIONS.APPEND
? fromYaml(yamlEditorValue).name
: mockScanResultObject.name,
projectPath: defaultProjectPath,
yamlEditorValue,
});
expect(visitUrl).toHaveBeenCalledWith(
`/${currentlyAssignedPolicyProject.fullPath}/-/merge_requests/2`,
);
},
);
});
describe('when a user is not an owner of the project', () => {
it('displays the empty state with the appropriate properties', async () => {
factory({ provide: { disableScanExecutionUpdate: true } });
expect(findEmptyState().props()).toMatchObject({
primaryButtonLink: scanExecutionDocumentationPath,
svgPath: policyEditorEmptyStateSvgPath,
});
});
});
});
......@@ -200,14 +200,14 @@ export const mockScanExecutionPolicy = {
latestScan: { date: new Date('2021-06-07T00:00:00.000Z'), pipelineUrl: 'path/to/pipeline' },
};
export const mockScanResultManifest = `type: scan_execution_policy
export const mockScanResultManifest = `type: scan_result_policy
name: critical vulnerability CS approvals
description: This policy enforces critical vulnerability CS approvals
enabled: true
rules:
- type: scan_finding
branches:
- master
- main
scanners:
- container_scanning
vulnerability_allowed: 1
......@@ -222,6 +222,30 @@ actions:
- the.one
`;
export const mockScanResultObject = {
type: 'scan_result_policy',
name: 'critical vulnerability CS approvals',
description: 'This policy enforces critical vulnerability CS approvals',
enabled: true,
rules: [
{
type: 'scan_finding',
branches: ['main'],
scanners: ['container_scanning'],
vulnerability_allowed: 1,
severity_levels: ['critical'],
vulnerability_states: ['newly_added'],
},
],
actions: [
{
type: 'require_approval',
approvals_required: 1,
user_approvers: ['the.one'],
},
],
};
export const mockScanResultPolicy = {
__typename: 'ScanResultPolicy',
name: 'critical vulnerability CS approvals',
......
......@@ -31803,6 +31803,9 @@ msgstr ""
msgid "SecurityOrchestration|Scan result"
msgstr ""
msgid "SecurityOrchestration|Scan result policies can only be created by project owners."
msgstr ""
msgid "SecurityOrchestration|Scan to be performed %{cadence}"
msgstr ""
......@@ -31848,7 +31851,7 @@ msgstr ""
msgid "SecurityOrchestration|Unlinking a security project removes all policies stored in the linked security project. Save to confirm this action."
msgstr ""
msgid "SecurityOrchestration|Update scan execution policies"
msgid "SecurityOrchestration|Update scan policies"
msgstr ""
msgid "SecurityOrchestration|a"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment