Commit 38873a13 authored by Alexander Turinske's avatar Alexander Turinske

Finish up refactor of policy editor

- ensure everything works properly
- clean up code
- update/add tests
parent e6c03ec6
<script> <script>
import { import { removeUnnecessaryDashes } from '../../utils';
fromYaml, import { fromYaml, humanizeNetworkPolicy } from '../policy_editor/network_policy/lib';
humanizeNetworkPolicy,
removeUnnecessaryDashes,
} from '../policy_editor/network_policy/lib';
import PolicyPreview from '../policy_editor/policy_preview.vue'; import PolicyPreview from '../policy_editor/policy_preview.vue';
import BasePolicy from './base_policy.vue'; import BasePolicy from './base_policy.vue';
import PolicyInfoRow from './policy_info_row.vue'; import PolicyInfoRow from './policy_info_row.vue';
......
<script> <script>
import { GlLink } from '@gitlab/ui'; import { GlLink } from '@gitlab/ui';
import { safeLoad } from 'js-yaml'; import { fromYaml } from '../policy_editor/scan_execution_policy/lib';
import BasePolicy from './base_policy.vue'; import BasePolicy from './base_policy.vue';
import PolicyInfoRow from './policy_info_row.vue'; import PolicyInfoRow from './policy_info_row.vue';
...@@ -18,7 +18,7 @@ export default { ...@@ -18,7 +18,7 @@ export default {
}, },
computed: { computed: {
policy() { policy() {
return safeLoad(this.value, { json: true }); return fromYaml(this.value);
}, },
}, },
}; };
......
import { s__, __ } from '~/locale'; import { s__, __ } from '~/locale';
export const EditorModeRule = 'rule'; export const EDITOR_MODE_RULE = 'rule';
export const EditorModeYAML = 'yaml'; export const EDITOR_MODE_YAML = 'yaml';
export const PARSING_ERROR_MESSAGE = s__( export const PARSING_ERROR_MESSAGE = s__(
'NetworkPolicies|Rule mode is unavailable for this policy. In some cases, we cannot parse the YAML file back into the rules editor.', 'NetworkPolicies|Rule mode is unavailable for this policy. In some cases, we cannot parse the YAML file back into the rules editor.',
); );
export const EDITOR_MODES = [ export const EDITOR_MODES = [
{ value: EditorModeRule, text: s__('NetworkPolicies|Rule mode') }, { value: EDITOR_MODE_RULE, text: s__('NetworkPolicies|Rule mode') },
{ value: EditorModeYAML, text: s__('NetworkPolicies|.yaml mode') }, { value: EDITOR_MODE_YAML, text: s__('NetworkPolicies|.yaml mode') },
]; ];
export const POLICY_TYPES = { export const POLICY_TYPES = {
......
<script> <script>
import { GlFormGroup, GlFormInput, GlFormTextarea, GlToggle, GlButton, GlAlert } from '@gitlab/ui'; import { GlFormGroup, GlFormInput, GlFormTextarea, GlToggle, GlButton, GlAlert } from '@gitlab/ui';
import { mapState, mapActions } from 'vuex'; import { mapState, mapActions } from 'vuex';
import { removeUnnecessaryDashes } from 'ee/threat_monitoring/utils';
import { redirectTo } from '~/lib/utils/url_utility'; import { redirectTo } from '~/lib/utils/url_utility';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { EDITOR_MODES, EditorModeYAML, PARSING_ERROR_MESSAGE } from '../constants'; import { EDITOR_MODES, EDITOR_MODE_YAML, PARSING_ERROR_MESSAGE } from '../constants';
import DimDisableContainer from '../dim_disable_container.vue'; import DimDisableContainer from '../dim_disable_container.vue';
import PolicyActionPicker from '../policy_action_picker.vue'; import PolicyActionPicker from '../policy_action_picker.vue';
import PolicyAlertPicker from '../policy_alert_picker.vue'; import PolicyAlertPicker from '../policy_alert_picker.vue';
import PolicyEditorLayout from '../policy_editor_layout.vue'; import PolicyEditorLayout from '../policy_editor_layout.vue';
import PolicyPreview from '../policy_preview.vue'; import PolicyPreview from '../policy_preview.vue';
import { removeUnnecessaryDashes } from '../utils';
import { import {
DEFAULT_NETWORK_POLICY, DEFAULT_NETWORK_POLICY,
RuleTypeEndpoint, RuleTypeEndpoint,
...@@ -128,14 +128,14 @@ export default { ...@@ -128,14 +128,14 @@ export default {
} }
}, },
changeEditorMode(mode) { changeEditorMode(mode) {
if (mode === EditorModeYAML && !this.hasParsingError) { if (mode === EDITOR_MODE_YAML && !this.hasParsingError) {
this.yamlEditorValue = toYaml(this.policy); this.yamlEditorValue = toYaml(this.policy);
} }
}, },
savePolicy(mode) { savePolicy(mode) {
const saveFn = this.isEditing ? this.updatePolicy : this.createPolicy; const saveFn = this.isEditing ? this.updatePolicy : this.createPolicy;
const policy = { const policy = {
manifest: mode === EditorModeYAML ? this.yamlEditorValue : toYaml(this.policy), manifest: mode === EDITOR_MODE_YAML ? this.yamlEditorValue : toYaml(this.policy),
}; };
if (this.isEditing) { if (this.isEditing) {
policy.name = this.existingPolicy.name; policy.name = this.existingPolicy.name;
...@@ -159,6 +159,7 @@ export default { ...@@ -159,6 +159,7 @@ export default {
<template> <template>
<policy-editor-layout <policy-editor-layout
:is-editing="isEditing" :is-editing="isEditing"
:is-removing-policy="isRemovingPolicy"
:is-updating-policy="isUpdatingPolicy" :is-updating-policy="isUpdatingPolicy"
:policy-name="policy.name" :policy-name="policy.name"
:yaml-editor-value="yamlEditorValue" :yaml-editor-value="yamlEditorValue"
......
<script> <script>
import { GlButton, GlFormGroup, GlModal, GlModalDirective, GlSegmentedControl } from '@gitlab/ui'; import { GlButton, GlFormGroup, GlModal, GlModalDirective, GlSegmentedControl } from '@gitlab/ui';
import { s__, sprintf } from '~/locale'; import { s__, sprintf } from '~/locale';
import { DELETE_MODAL_CONFIG, EDITOR_MODES, EditorModeRule, EditorModeYAML } from './constants'; import { DELETE_MODAL_CONFIG, EDITOR_MODES, EDITOR_MODE_RULE, EDITOR_MODE_YAML } from './constants';
export default { export default {
i18n: { i18n: {
...@@ -18,15 +18,10 @@ export default { ...@@ -18,15 +18,10 @@ export default {
directives: { GlModal: GlModalDirective }, directives: { GlModal: GlModalDirective },
inject: ['threatMonitoringPath'], inject: ['threatMonitoringPath'],
props: { props: {
customSaveButtonText: {
type: String,
required: false,
default: '',
},
defaultEditorMode: { defaultEditorMode: {
type: String, type: String,
required: false, required: false,
default: EditorModeRule, default: EDITOR_MODE_RULE,
}, },
editorModes: { editorModes: {
type: Array, type: Array,
...@@ -38,6 +33,11 @@ export default { ...@@ -38,6 +33,11 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
isRemovingPolicy: {
type: Boolean,
required: false,
default: false,
},
isUpdatingPolicy: { isUpdatingPolicy: {
type: Boolean, type: Boolean,
required: false, required: false,
...@@ -64,18 +64,15 @@ export default { ...@@ -64,18 +64,15 @@ export default {
return sprintf(s__('NetworkPolicies|Delete policy: %{policy}'), { policy: this.policyName }); return sprintf(s__('NetworkPolicies|Delete policy: %{policy}'), { policy: this.policyName });
}, },
saveButtonText() { saveButtonText() {
if (this.customSaveButtonText) {
return this.customSaveButtonText;
}
return this.isEditing return this.isEditing
? s__('NetworkPolicies|Save changes') ? s__('NetworkPolicies|Save changes')
: s__('NetworkPolicies|Create policy'); : s__('NetworkPolicies|Create policy');
}, },
shouldShowRuleEditor() { shouldShowRuleEditor() {
return this.selectedEditorMode === EditorModeRule; return this.selectedEditorMode === EDITOR_MODE_RULE;
}, },
shouldShowYamlEditor() { shouldShowYamlEditor() {
return this.selectedEditorMode === EditorModeYAML; return this.selectedEditorMode === EDITOR_MODE_YAML;
}, },
}, },
methods: { methods: {
...@@ -89,8 +86,8 @@ export default { ...@@ -89,8 +86,8 @@ export default {
this.selectedEditorMode = mode; this.selectedEditorMode = mode;
this.$emit('update-editor-mode', mode); this.$emit('update-editor-mode', mode);
}, },
updateYaml() { updateYaml(manifest) {
this.$emit('load-yaml'); this.$emit('update-yaml', manifest);
}, },
}, },
}; };
...@@ -103,7 +100,6 @@ export default { ...@@ -103,7 +100,6 @@ export default {
class="gl-px-5 gl-py-3 gl-mb-0 gl-bg-gray-10 gl-border-b-solid gl-border-b-gray-100 gl-border-b-1" class="gl-px-5 gl-py-3 gl-mb-0 gl-bg-gray-10 gl-border-b-solid gl-border-b-gray-100 gl-border-b-1"
> >
<gl-segmented-control <gl-segmented-control
data-testid="editor-mode"
:options="editorModes" :options="editorModes"
:checked="selectedEditorMode" :checked="selectedEditorMode"
@input="updateEditorMode" @input="updateEditorMode"
...@@ -111,7 +107,9 @@ export default { ...@@ -111,7 +107,9 @@ export default {
</gl-form-group> </gl-form-group>
<div class="gl-display-flex gl-sm-flex-direction-column"> <div class="gl-display-flex gl-sm-flex-direction-column">
<section class="gl-w-full gl-p-5 gl-flex-fill-4 policy-table-left"> <section class="gl-w-full gl-p-5 gl-flex-fill-4 policy-table-left">
<slot v-if="shouldShowRuleEditor" name="rule-editor" data-testid="rule-editor"></slot> <div v-if="shouldShowRuleEditor" data-testid="rule-editor">
<slot name="rule-editor"></slot>
</div>
<policy-yaml-editor <policy-yaml-editor
v-if="shouldShowYamlEditor" v-if="shouldShowYamlEditor"
data-testid="policy-yaml-editor" data-testid="policy-yaml-editor"
...@@ -123,6 +121,7 @@ export default { ...@@ -123,6 +121,7 @@ export default {
<section <section
v-if="shouldShowRuleEditor" v-if="shouldShowRuleEditor"
class="gl-w-30p gl-p-5 gl-border-l-gray-100 gl-border-l-1 gl-border-l-solid gl-flex-fill-2" class="gl-w-30p gl-p-5 gl-border-l-gray-100 gl-border-l-1 gl-border-l-solid gl-flex-fill-2"
data-testid="rule-editor-preview"
> >
<slot name="rule-editor-preview"></slot> <slot name="rule-editor-preview"></slot>
</section> </section>
...@@ -134,8 +133,11 @@ export default { ...@@ -134,8 +133,11 @@ export default {
data-testid="save-policy" data-testid="save-policy"
:loading="isUpdatingPolicy" :loading="isUpdatingPolicy"
@click="savePolicy" @click="savePolicy"
>{{ saveButtonText }}</gl-button
> >
<slot name="save-button-text">
{{ saveButtonText }}
</slot>
</gl-button>
<gl-button <gl-button
v-if="isEditing" v-if="isEditing"
v-gl-modal="'delete-modal'" v-gl-modal="'delete-modal'"
......
<script> <script>
import { removeUnnecessaryDashes } from 'ee/threat_monitoring/utils';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { EDITOR_MODES, EditorModeYAML } from '../constants'; import { EDITOR_MODES, EDITOR_MODE_YAML } from '../constants';
import PolicyEditorLayout from '../policy_editor_layout.vue'; import PolicyEditorLayout from '../policy_editor_layout.vue';
import { DEFAULT_SCAN_EXECUTION_POLICY, fromYaml } from './lib'; import { DEFAULT_SCAN_EXECUTION_POLICY, fromYaml } from './lib';
export default { export default {
DEFAULT_EDITOR_MODE: EditorModeYAML, DEFAULT_EDITOR_MODE: EDITOR_MODE_YAML,
EDITOR_MODES: [EDITOR_MODES[1]], EDITOR_MODES: [EDITOR_MODES[1]],
i18n: { i18n: {
createMergeRequest: __('Create merge request'), createMergeRequest: __('Create merge request'),
...@@ -27,28 +28,30 @@ export default { ...@@ -27,28 +28,30 @@ export default {
: fromYaml(DEFAULT_SCAN_EXECUTION_POLICY); : fromYaml(DEFAULT_SCAN_EXECUTION_POLICY);
const yamlEditorValue = this.existingPolicy const yamlEditorValue = this.existingPolicy
? this.existingPolicy.manifest ? removeUnnecessaryDashes(this.existingPolicy.manifest)
: DEFAULT_SCAN_EXECUTION_POLICY; : DEFAULT_SCAN_EXECUTION_POLICY;
return { return {
editorMode: EditorModeYAML, isRemovingPolicy: false,
yamlEditorValue, isUpdatingPolicy: false,
yamlEditorError: policy.error ? true : null,
policy, policy,
yamlEditorValue,
}; };
}, },
computed: { computed: {
isCreatingMergeRequest() {
// TODO track the graphql mutation status after #333163 is closed
return false;
},
isEditing() { isEditing() {
return Boolean(this.existingPolicy); return Boolean(this.existingPolicy);
}, },
}, },
methods: { methods: {
createMergeRequest() { removePolicy() {
// TODO call graphql mutation and redirect to merge request after #333163 is closed // TODO call graphql mutation and redirect to merge request after #329422 is closed
},
savePolicy() {
// TODO call graphql mutation and redirect to merge request after #329422 is closed
},
updateYaml(manifest) {
this.yamlEditorValue = manifest;
}, },
}, },
}; };
...@@ -56,13 +59,19 @@ export default { ...@@ -56,13 +59,19 @@ export default {
<template> <template>
<policy-editor-layout <policy-editor-layout
:custom-save-button-text="$options.i18n.createMergeRequest"
:default-editor-mode="$options.DEFAULT_EDITOR_MODE" :default-editor-mode="$options.DEFAULT_EDITOR_MODE"
:editor-modes="$options.EDITOR_MODES" :editor-modes="$options.EDITOR_MODES"
:is-editing="isEditing" :is-editing="isEditing"
:is-updating-policy="isCreatingMergeRequest" :is-removing-policy="isRemovingPolicy"
:is-updating-policy="isUpdatingPolicy"
:policy-name="policy.name" :policy-name="policy.name"
:yaml-editor-value="yamlEditorValue" :yaml-editor-value="yamlEditorValue"
@save-policy="createMergeRequest" @remove-policy="removePolicy"
/> @save-policy="savePolicy"
@update-yaml="updateYaml"
>
<template #save-button-text>
{{ $options.i18n.createMergeRequest }}
</template>
</policy-editor-layout>
</template> </template>
/**
* Removes inital line dashes from a policy YAML that is received from the API, which
* is not required for the user.
* @param {String} manifest the policy from the API request
* @returns {String} the policy without the initial dashes or the initial string
*/
export const removeUnnecessaryDashes = (manifest) => {
return manifest.replace('---\n', '');
};
...@@ -9,3 +9,13 @@ export const getContentWrapperHeight = (contentWrapperClass) => { ...@@ -9,3 +9,13 @@ export const getContentWrapperHeight = (contentWrapperClass) => {
const wrapperEl = document.querySelector(contentWrapperClass); const wrapperEl = document.querySelector(contentWrapperClass);
return wrapperEl ? `${wrapperEl.offsetTop}px` : ''; return wrapperEl ? `${wrapperEl.offsetTop}px` : '';
}; };
/**
* Removes inital line dashes from a policy YAML that is received from the API, which
* is not required for the user.
* @param {String} manifest the policy from the API request
* @returns {String} the policy without the initial dashes or the initial string
*/
export const removeUnnecessaryDashes = (manifest) => {
return manifest.replace('---\n', '');
};
...@@ -4,5 +4,5 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63585 ...@@ -4,5 +4,5 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63585
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/273791 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/273791
milestone: '14.0' milestone: '14.0'
type: development type: development
group: group::container_security group: group::container security
default_enabled: false default_enabled: false
...@@ -11,7 +11,6 @@ import { ...@@ -11,7 +11,6 @@ import {
RuleTypeFQDN, RuleTypeFQDN,
EntityTypes, EntityTypes,
fromYaml, fromYaml,
removeUnnecessaryDashes,
buildRule, buildRule,
toYaml, toYaml,
} from 'ee/threat_monitoring/components/policy_editor/network_policy/lib'; } from 'ee/threat_monitoring/components/policy_editor/network_policy/lib';
...@@ -332,15 +331,3 @@ spec: ...@@ -332,15 +331,3 @@ spec:
); );
}); });
}); });
describe('removeUnnecessaryDashes', () => {
it.each`
input | output
${'---\none'} | ${'one'}
${'two'} | ${'two'}
${'--\nthree'} | ${'--\nthree'}
${'four---\n'} | ${'four'}
`('returns $output when used on $input', ({ input, output }) => {
expect(removeUnnecessaryDashes(input)).toBe(output);
});
});
import { GlModal, GlToggle } from '@gitlab/ui'; import { GlToggle } from '@gitlab/ui';
import { EditorModeYAML } from 'ee/threat_monitoring/components/policy_editor/constants'; import { EDITOR_MODE_YAML } from 'ee/threat_monitoring/components/policy_editor/constants';
import { import {
RuleDirectionInbound, RuleDirectionInbound,
PortMatchModeAny, PortMatchModeAny,
...@@ -12,35 +12,18 @@ import { ...@@ -12,35 +12,18 @@ import {
import NetworkPolicyEditor from 'ee/threat_monitoring/components/policy_editor/network_policy/network_policy_editor.vue'; import NetworkPolicyEditor from 'ee/threat_monitoring/components/policy_editor/network_policy/network_policy_editor.vue';
import PolicyRuleBuilder from 'ee/threat_monitoring/components/policy_editor/network_policy/policy_rule_builder.vue'; import PolicyRuleBuilder from 'ee/threat_monitoring/components/policy_editor/network_policy/policy_rule_builder.vue';
import PolicyAlertPicker from 'ee/threat_monitoring/components/policy_editor/policy_alert_picker.vue'; import PolicyAlertPicker from 'ee/threat_monitoring/components/policy_editor/policy_alert_picker.vue';
import PolicyEditorLayout from 'ee/threat_monitoring/components/policy_editor/policy_editor_layout.vue';
import PolicyPreview from 'ee/threat_monitoring/components/policy_editor/policy_preview.vue'; import PolicyPreview from 'ee/threat_monitoring/components/policy_editor/policy_preview.vue';
import createStore from 'ee/threat_monitoring/store'; import createStore from 'ee/threat_monitoring/store';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { redirectTo } from '~/lib/utils/url_utility'; import { redirectTo } from '~/lib/utils/url_utility';
import { mockL3Manifest, mockL7Manifest } from '../../../mocks/mock_data';
jest.mock('~/lib/utils/url_utility'); jest.mock('~/lib/utils/url_utility');
describe('NetworkPolicyEditor component', () => { describe('NetworkPolicyEditor component', () => {
let store; let store;
let wrapper; let wrapper;
const l7manifest = `apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: limit-inbound-ip
spec:
endpointSelector: {}
ingress:
- toPorts:
- ports:
- port: '80'
protocol: TCP
- port: '443'
protocol: TCP
rules:
http:
- headers:
- 'X-Forwarded-For: 192.168.1.1'
fromEntities:
- cluster`;
const factory = ({ propsData, provide = {}, state, data } = {}) => { const factory = ({ propsData, provide = {}, state, data } = {}) => {
store = createStore(); store = createStore();
...@@ -55,11 +38,11 @@ spec: ...@@ -55,11 +38,11 @@ spec:
wrapper = shallowMountExtended(NetworkPolicyEditor, { wrapper = shallowMountExtended(NetworkPolicyEditor, {
propsData: { propsData: {
threatMonitoringPath: '/threat-monitoring',
projectId: '21',
...propsData, ...propsData,
}, },
provide: { provide: {
threatMonitoringPath: '/threat-monitoring',
projectId: '21',
...provide, ...provide,
}, },
store, store,
...@@ -68,26 +51,22 @@ spec: ...@@ -68,26 +51,22 @@ spec:
}); });
}; };
const findRuleEditor = () => wrapper.findByTestId('rule-editor');
const findPreview = () => wrapper.findComponent(PolicyPreview); const findPreview = () => wrapper.findComponent(PolicyPreview);
const findAddRuleButton = () => wrapper.findByTestId('add-rule'); const findAddRuleButton = () => wrapper.findByTestId('add-rule');
const findYAMLParsingAlert = () => wrapper.findByTestId('parsing-alert'); const findYAMLParsingAlert = () => wrapper.findByTestId('parsing-alert');
const findPolicyYamlEditor = () => wrapper.findByTestId('policy-yaml-editor');
const findPolicyAlertPicker = () => wrapper.findComponent(PolicyAlertPicker); const findPolicyAlertPicker = () => wrapper.findComponent(PolicyAlertPicker);
const findPolicyDescription = () => wrapper.find("[id='policyDescription']"); const findPolicyDescription = () => wrapper.find("[id='policyDescription']");
const findPolicyEnableContainer = () => wrapper.findByTestId('policy-enable'); const findPolicyEnableContainer = () => wrapper.findByTestId('policy-enable');
const findPolicyName = () => wrapper.find("[id='policyName']"); const findPolicyName = () => wrapper.find("[id='policyName']");
const findPolicyRuleBuilder = () => wrapper.findComponent(PolicyRuleBuilder); const findPolicyRuleBuilder = () => wrapper.findComponent(PolicyRuleBuilder);
const findSavePolicy = () => wrapper.findByTestId('save-policy'); const findPolicyEditorLayout = () => wrapper.findComponent(PolicyEditorLayout);
const findDeletePolicy = () => wrapper.findByTestId('delete-policy');
const findEditorModeToggle = () => wrapper.findByTestId('editor-mode');
const modifyPolicyAlert = async ({ isAlertEnabled }) => { const modifyPolicyAlert = async ({ isAlertEnabled }) => {
const policyAlertPicker = findPolicyAlertPicker(); const policyAlertPicker = findPolicyAlertPicker();
policyAlertPicker.vm.$emit('update-alert', isAlertEnabled); policyAlertPicker.vm.$emit('update-alert', isAlertEnabled);
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
expect(policyAlertPicker.props('policyAlert')).toBe(isAlertEnabled); expect(policyAlertPicker.props('policyAlert')).toBe(isAlertEnabled);
findSavePolicy().vm.$emit('click'); findPolicyEditorLayout().vm.$emit('save-policy');
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
}; };
...@@ -117,14 +96,10 @@ spec: ...@@ -117,14 +96,10 @@ spec:
it.each` it.each`
component | status | findComponent | state component | status | findComponent | state
${'policy alert picker'} | ${'does display'} | ${findPolicyAlertPicker} | ${true} ${'policy alert picker'} | ${'does display'} | ${findPolicyAlertPicker} | ${true}
${'editor mode toggle'} | ${'does display'} | ${findEditorModeToggle} | ${true}
${'policy name input'} | ${'does display'} | ${findPolicyName} | ${true} ${'policy name input'} | ${'does display'} | ${findPolicyName} | ${true}
${'rule editor'} | ${'does display'} | ${findRuleEditor} | ${true}
${'add rule button'} | ${'does display'} | ${findAddRuleButton} | ${true} ${'add rule button'} | ${'does display'} | ${findAddRuleButton} | ${true}
${'policy preview'} | ${'does display'} | ${findPreview} | ${true} ${'policy preview'} | ${'does display'} | ${findPreview} | ${true}
${'yaml editor'} | ${'does not display'} | ${findPolicyYamlEditor} | ${false}
${'parsing error alert'} | ${'does not display'} | ${findYAMLParsingAlert} | ${false} ${'parsing error alert'} | ${'does not display'} | ${findYAMLParsingAlert} | ${false}
${'delete button'} | ${'does not display'} | ${findDeletePolicy} | ${false}
`('$status the $component', async ({ findComponent, state }) => { `('$status the $component', async ({ findComponent, state }) => {
expect(findComponent().exists()).toBe(state); expect(findComponent().exists()).toBe(state);
}); });
...@@ -133,38 +108,13 @@ spec: ...@@ -133,38 +108,13 @@ spec:
beforeEach(() => { beforeEach(() => {
factory({ factory({
data: () => ({ data: () => ({
editorMode: EditorModeYAML, editorMode: EDITOR_MODE_YAML,
}), }),
}); });
}); });
it.each`
component | status | findComponent | state
${'editor mode toggle'} | ${'does display'} | ${findEditorModeToggle} | ${true}
${'rule editor'} | ${'does not display'} | ${findRuleEditor} | ${false}
${'yaml editor'} | ${'does display'} | ${findPolicyYamlEditor} | ${true}
`('$status the $component', ({ findComponent, state }) => {
expect(findComponent().exists()).toBe(state);
});
it('updates policy on yaml editor value change', async () => { it('updates policy on yaml editor value change', async () => {
const manifest = `apiVersion: cilium.io/v2 findPolicyEditorLayout().vm.$emit('update-yaml', mockL3Manifest);
kind: CiliumNetworkPolicy
description: test description
metadata:
name: test-policy
labels:
app.gitlab.com/proj: '21'
spec:
endpointSelector:
matchLabels:
network-policy.gitlab.com/disabled_by: gitlab
foo: bar
ingress:
- fromEndpoints:
- matchLabels:
foo: bar`;
findPolicyYamlEditor().vm.$emit('input', manifest);
expect(wrapper.vm.policy).toMatchObject({ expect(wrapper.vm.policy).toMatchObject({
name: 'test-policy', name: 'test-policy',
...@@ -185,16 +135,16 @@ spec: ...@@ -185,16 +135,16 @@ spec:
it('saves L7 policies', async () => { it('saves L7 policies', async () => {
factory({ factory({
data: () => ({ data: () => ({
editorMode: EditorModeYAML, editorMode: EDITOR_MODE_YAML,
yamlEditorValue: l7manifest, yamlEditorValue: mockL7Manifest,
}), }),
}); });
findSavePolicy().vm.$emit('click');
findPolicyEditorLayout().vm.$emit('save-policy', EDITOR_MODE_YAML);
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
expect(store.dispatch).toHaveBeenCalledWith('networkPolicies/createPolicy', { expect(store.dispatch).toHaveBeenCalledWith('networkPolicies/createPolicy', {
environmentId: -1, environmentId: -1,
policy: { manifest: l7manifest }, policy: { manifest: mockL7Manifest },
}); });
expect(redirectTo).toHaveBeenCalledWith('/threat-monitoring'); expect(redirectTo).toHaveBeenCalledWith('/threat-monitoring');
}); });
...@@ -246,13 +196,11 @@ spec: ...@@ -246,13 +196,11 @@ spec:
}); });
it('updates yaml editor value on switch to yaml editor', async () => { it('updates yaml editor value on switch to yaml editor', async () => {
const policyEditorLayout = findPolicyEditorLayout();
findPolicyName().vm.$emit('input', 'test-policy'); findPolicyName().vm.$emit('input', 'test-policy');
findEditorModeToggle().vm.$emit('input', EditorModeYAML); policyEditorLayout.vm.$emit('update-editor-mode', EDITOR_MODE_YAML);
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
expect(fromYaml(policyEditorLayout.attributes('yamleditorvalue'))).toMatchObject({
const editor = findPolicyYamlEditor();
expect(editor.exists()).toBe(true);
expect(fromYaml(editor.attributes('value'))).toMatchObject({
name: 'test-policy', name: 'test-policy',
}); });
}); });
...@@ -296,17 +244,15 @@ spec: ...@@ -296,17 +244,15 @@ spec:
it('does not update yaml editor value on switch to yaml editor', async () => { it('does not update yaml editor value on switch to yaml editor', async () => {
findPolicyName().vm.$emit('input', 'test-policy'); findPolicyName().vm.$emit('input', 'test-policy');
findEditorModeToggle().vm.$emit('input', EditorModeYAML); const policyEditorLayout = findPolicyEditorLayout();
policyEditorLayout.vm.$emit('update-editor-mode', EDITOR_MODE_YAML);
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
expect(policyEditorLayout.attributes('yamleditorvalue')).toEqual('');
const editor = findPolicyYamlEditor();
expect(editor.exists()).toBe(true);
expect(editor.attributes('value')).toEqual('');
}); });
}); });
it('creates policy and redirects to a threat monitoring path', async () => { it('creates policy and redirects to a threat monitoring path', async () => {
findSavePolicy().vm.$emit('click'); findPolicyEditorLayout().vm.$emit('save-policy');
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
expect(store.dispatch).toHaveBeenCalledWith('networkPolicies/createPolicy', { expect(store.dispatch).toHaveBeenCalledWith('networkPolicies/createPolicy', {
...@@ -326,8 +272,7 @@ spec: ...@@ -326,8 +272,7 @@ spec:
}); });
it('it does not redirect', async () => { it('it does not redirect', async () => {
findSavePolicy().vm.$emit('click'); findPolicyEditorLayout().vm.$emit('save-policy');
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
expect(redirectTo).not.toHaveBeenCalledWith('/threat-monitoring'); expect(redirectTo).not.toHaveBeenCalledWith('/threat-monitoring');
}); });
...@@ -354,10 +299,7 @@ spec: ...@@ -354,10 +299,7 @@ spec:
}); });
it('updates existing policy and redirects to a threat monitoring path', async () => { it('updates existing policy and redirects to a threat monitoring path', async () => {
const saveButton = findSavePolicy(); findPolicyEditorLayout().vm.$emit('save-policy');
expect(saveButton.text()).toEqual('Save changes');
saveButton.vm.$emit('click');
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
expect(store.dispatch).toHaveBeenCalledWith('networkPolicies/updatePolicy', { expect(store.dispatch).toHaveBeenCalledWith('networkPolicies/updatePolicy', {
environmentId: -1, environmentId: -1,
...@@ -379,26 +321,14 @@ spec: ...@@ -379,26 +321,14 @@ spec:
}); });
it('it does not redirect', async () => { it('it does not redirect', async () => {
findSavePolicy().vm.$emit('click'); findPolicyEditorLayout().vm.$emit('save-policy');
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
expect(redirectTo).not.toHaveBeenCalledWith('/threat-monitoring'); expect(redirectTo).not.toHaveBeenCalledWith('/threat-monitoring');
}); });
}); });
it('renders delete button', () => {
expect(findDeletePolicy().exists()).toBe(true);
});
it('it does not trigger deletePolicy on delete button click', async () => {
findDeletePolicy().vm.$emit('click');
await wrapper.vm.$nextTick();
expect(store.dispatch).not.toHaveBeenCalledWith('networkPolicies/deletePolicy');
});
it('removes policy and redirects to a threat monitoring path on secondary modal button click', async () => { it('removes policy and redirects to a threat monitoring path on secondary modal button click', async () => {
wrapper.findComponent(GlModal).vm.$emit('secondary'); findPolicyEditorLayout().vm.$emit('remove-policy');
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
expect(store.dispatch).toHaveBeenCalledWith('networkPolicies/deletePolicy', { expect(store.dispatch).toHaveBeenCalledWith('networkPolicies/deletePolicy', {
......
import { GlModal, GlSegmentedControl } from '@gitlab/ui';
import { EDITOR_MODE_YAML } from 'ee/threat_monitoring/components/policy_editor/constants';
import PolicyEditorLayout from 'ee/threat_monitoring/components/policy_editor/policy_editor_layout.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
describe('PolicyEditorLayout component', () => {
let wrapper;
const threatMonitoringPath = '/threat-monitoring';
const factory = ({ propsData = {} } = {}) => {
wrapper = shallowMountExtended(PolicyEditorLayout, {
propsData: {
...propsData,
},
provide: {
threatMonitoringPath,
},
stubs: { PolicyYamlEditor: true },
});
};
const findDeletePolicyButton = () => wrapper.findByTestId('delete-policy');
const findDeletePolicyModal = () => wrapper.findComponent(GlModal);
const findEditorModeToggle = () => wrapper.findComponent(GlSegmentedControl);
const findYamlModeSection = () => wrapper.findByTestId('policy-yaml-editor');
const findRuleModeSection = () => wrapper.findByTestId('rule-editor');
const findRuleModePreviewSection = () => wrapper.findByTestId('rule-editor-preview');
const findSavePolicyButton = () => wrapper.findByTestId('save-policy');
afterEach(() => {
wrapper.destroy();
});
describe('default behavior', () => {
beforeEach(() => {
factory();
});
it.each`
component | status | findComponent | state
${'editor mode toggle'} | ${'does display'} | ${findEditorModeToggle} | ${true}
${'delete button'} | ${'does not display'} | ${findDeletePolicyButton} | ${false}
`('$status the $component', async ({ findComponent, state }) => {
expect(findComponent().exists()).toBe(state);
});
it('does display the correct save button text when creating a new policy', () => {
const saveButton = findSavePolicyButton();
expect(saveButton.exists()).toBe(true);
expect(saveButton.text()).toBe('Create policy');
});
it('emits properly with the current mode when the save button is clicked', () => {
findSavePolicyButton().vm.$emit('click');
expect(wrapper.emitted('save-policy')).toStrictEqual([['rule']]);
});
it('mode changes appropriately when new mode is selected', async () => {
expect(findRuleModeSection().exists()).toBe(true);
expect(findYamlModeSection().exists()).toBe(false);
findEditorModeToggle().vm.$emit('input', EDITOR_MODE_YAML);
await wrapper.vm.$nextTick();
expect(findRuleModeSection().exists()).toBe(false);
expect(findYamlModeSection().exists()).toBe(true);
expect(wrapper.emitted('update-editor-mode')).toStrictEqual([[EDITOR_MODE_YAML]]);
});
it('does display custom save button text', () => {
// custom save button text works
const saveButton = findSavePolicyButton();
expect(saveButton.exists()).toBe(true);
expect(saveButton.text()).toBe('Create policy');
});
});
describe('editing a policy', () => {
beforeEach(() => {
factory({ propsData: { isEditing: true } });
});
it('does not emit when the delete button is clicked', () => {
findDeletePolicyButton().vm.$emit('click');
expect(wrapper.emitted('remove-policy')).toStrictEqual(undefined);
});
it('emits properly when the delete modal is closed', () => {
findDeletePolicyModal().vm.$emit('secondary');
expect(wrapper.emitted('remove-policy')).toStrictEqual([[]]);
});
});
describe('rule mode', () => {
beforeEach(() => {
factory();
});
it.each`
component | status | findComponent | state
${'rule mode section'} | ${'does display'} | ${findRuleModeSection} | ${true}
${'rule mode preview section'} | ${'does display'} | ${findRuleModePreviewSection} | ${true}
${'yaml mode section'} | ${'does not display'} | ${findYamlModeSection} | ${false}
`('$status the $component', async ({ findComponent, state }) => {
expect(findComponent().exists()).toBe(state);
});
});
describe('yaml mode', () => {
beforeEach(() => {
factory({ propsData: { defaultEditorMode: EDITOR_MODE_YAML } });
});
it.each`
component | status | findComponent | state
${'rule mode section'} | ${'does not display'} | ${findRuleModeSection} | ${false}
${'rule mode preview section'} | ${'does not display'} | ${findRuleModePreviewSection} | ${false}
${'yaml mode section'} | ${'does display'} | ${findYamlModeSection} | ${true}
`('$status the $component', async ({ findComponent, state }) => {
expect(findComponent().exists()).toBe(state);
});
it('emits propertly when yaml is updated', () => {
const newManifest = 'new yaml!';
findYamlModeSection().vm.$emit('input', newManifest);
expect(wrapper.emitted('update-yaml')).toStrictEqual([[newManifest]]);
});
});
});
...@@ -14,7 +14,7 @@ describe('PolicyEditor component', () => { ...@@ -14,7 +14,7 @@ describe('PolicyEditor component', () => {
const findFormSelect = () => wrapper.findComponent(GlFormSelect); const findFormSelect = () => wrapper.findComponent(GlFormSelect);
const findNeworkPolicyEditor = () => wrapper.findComponent(NetworkPolicyEditor); const findNeworkPolicyEditor = () => wrapper.findComponent(NetworkPolicyEditor);
const factory = ({ propsData } = {}) => { const factory = ({ propsData = {}, provide = {} } = {}) => {
store = createStore(); store = createStore();
jest.spyOn(store, 'dispatch').mockImplementation(() => Promise.resolve()); jest.spyOn(store, 'dispatch').mockImplementation(() => Promise.resolve());
...@@ -25,18 +25,19 @@ describe('PolicyEditor component', () => { ...@@ -25,18 +25,19 @@ describe('PolicyEditor component', () => {
projectId: '21', projectId: '21',
...propsData, ...propsData,
}, },
provide,
store, store,
stubs: { GlFormSelect }, stubs: { GlFormSelect },
}); });
}; };
beforeEach(factory);
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
}); });
describe('default', () => { describe('default', () => {
beforeEach(factory);
it('renders the environment picker', () => { it('renders the environment picker', () => {
expect(findEnvironmentPicker().exists()).toBe(true); expect(findEnvironmentPicker().exists()).toBe(true);
}); });
...@@ -45,10 +46,23 @@ describe('PolicyEditor component', () => { ...@@ -45,10 +46,23 @@ describe('PolicyEditor component', () => {
const formSelect = findFormSelect(); const formSelect = findFormSelect();
expect(formSelect.exists()).toBe(true); expect(formSelect.exists()).toBe(true);
expect(formSelect.attributes('value')).toBe(POLICY_TYPES.networkPolicy.value); expect(formSelect.attributes('value')).toBe(POLICY_TYPES.networkPolicy.value);
expect(formSelect.attributes('disabled')).toBe('true');
}); });
it('renders the "NetworkPolicyEditor" component', () => { it('renders the "NetworkPolicyEditor" component', () => {
expect(findNeworkPolicyEditor().exists()).toBe(true); expect(findNeworkPolicyEditor().exists()).toBe(true);
}); });
}); });
describe('with "scanExecutionPolicyUi" feature flag enabled', () => {
beforeEach(() => {
factory({ provide: { glFeatures: { scanExecutionPolicyUi: true } } });
});
it('renders the form select', () => {
const formSelect = findFormSelect();
expect(formSelect.exists()).toBe(true);
expect(formSelect.attributes('disabled')).toBe(undefined);
});
});
}); });
import {
DEFAULT_SCAN_EXECUTION_POLICY,
fromYaml,
} from 'ee/threat_monitoring/components/policy_editor/scan_execution_policy/lib';
describe('fromYaml', () => {
it('returns policy object', () => {
expect(fromYaml(DEFAULT_SCAN_EXECUTION_POLICY)).toMatchObject({
name: '',
description: '',
enabled: false,
actions: [{ scan: 'dast', site_profile: '', scanner_profile: '' }],
rules: [{ branches: ['main'], type: 'pipeline' }],
});
});
});
import { shallowMount } from '@vue/test-utils';
import PolicyEditorLayout from 'ee/threat_monitoring/components/policy_editor/policy_editor_layout.vue';
import { DEFAULT_SCAN_EXECUTION_POLICY } 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 waitForPromises from 'helpers/wait_for_promises';
describe('ScanExecutionPolicyEditor', () => {
let wrapper;
const factory = ({ propsData = {} } = {}) => {
wrapper = shallowMount(ScanExecutionPolicyEditor, {
propsData,
provide: {
threatMonitoringPath: '',
projectId: 1,
},
});
};
const findPolicyEditorLayout = () => wrapper.findComponent(PolicyEditorLayout);
beforeEach(() => {
factory();
});
afterEach(() => {
wrapper.destroy();
});
it('calls the save policy funtion when "save-policy" is emitted', async () => {
const savePolicySpy = jest.spyOn(wrapper.vm, 'savePolicy');
expect(wrapper.vm.savePolicy).toHaveBeenCalledTimes(0);
findPolicyEditorLayout().vm.$emit('save-policy');
await waitForPromises();
expect(wrapper.vm.savePolicy).toHaveBeenCalledTimes(1);
savePolicySpy.mockRestore();
});
it('calls the remove policy funtion when "remove-policy" is emitted', async () => {
const removePolicySpy = jest.spyOn(wrapper.vm, 'removePolicy');
expect(wrapper.vm.removePolicy).toHaveBeenCalledTimes(0);
findPolicyEditorLayout().vm.$emit('remove-policy');
await waitForPromises();
expect(wrapper.vm.removePolicy).toHaveBeenCalledTimes(1);
removePolicySpy.mockRestore();
});
it('updates the policy yaml when "update-yaml" is emitted', async () => {
const updateYamlSpy = jest.spyOn(wrapper.vm, 'updateYaml');
const newManifest = 'new yaml!';
expect(wrapper.vm.updateYaml).toHaveBeenCalledTimes(0);
expect(findPolicyEditorLayout().attributes('yamleditorvalue')).toBe(
DEFAULT_SCAN_EXECUTION_POLICY,
);
findPolicyEditorLayout().vm.$emit('update-yaml', newManifest);
await waitForPromises();
expect(wrapper.vm.updateYaml).toHaveBeenCalledTimes(1);
expect(wrapper.vm.updateYaml).toHaveBeenCalledWith(newManifest);
expect(findPolicyEditorLayout().attributes('yaml-editor-value')).toBe(newManifest);
updateYamlSpy.mockRestore();
});
});
...@@ -181,3 +181,40 @@ export const mockAlertDetails = { ...@@ -181,3 +181,40 @@ export const mockAlertDetails = {
title: 'dropingress', title: 'dropingress',
monitorTool: 'Cilium', monitorTool: 'Cilium',
}; };
export const mockL7Manifest = `apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: limit-inbound-ip
spec:
endpointSelector: {}
ingress:
- toPorts:
- ports:
- port: '80'
protocol: TCP
- port: '443'
protocol: TCP
rules:
http:
- headers:
- 'X-Forwarded-For: 192.168.1.1'
fromEntities:
- cluster`;
export const mockL3Manifest = `apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
description: test description
metadata:
name: test-policy
labels:
app.gitlab.com/proj: '21'
spec:
endpointSelector:
matchLabels:
network-policy.gitlab.com/disabled_by: gitlab
foo: bar
ingress:
- fromEndpoints:
- matchLabels:
foo: bar`;
import { getContentWrapperHeight } from 'ee/threat_monitoring/utils'; import { getContentWrapperHeight, removeUnnecessaryDashes } from 'ee/threat_monitoring/utils';
import { setHTMLFixture } from 'helpers/fixtures'; import { setHTMLFixture } from 'helpers/fixtures';
describe('Threat Monitoring Utils', () => { describe('Threat Monitoring Utils', () => {
...@@ -23,4 +23,16 @@ describe('Threat Monitoring Utils', () => { ...@@ -23,4 +23,16 @@ describe('Threat Monitoring Utils', () => {
expect(getContentWrapperHeight('.does-not-exist')).toBe(''); expect(getContentWrapperHeight('.does-not-exist')).toBe('');
}); });
}); });
describe('removeUnnecessaryDashes', () => {
it.each`
input | output
${'---\none'} | ${'one'}
${'two'} | ${'two'}
${'--\nthree'} | ${'--\nthree'}
${'four---\n'} | ${'four'}
`('returns $output when used on $input', ({ input, output }) => {
expect(removeUnnecessaryDashes(input)).toBe(output);
});
});
}); });
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