Commit 0c78bd7a authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '271169-update-yaml-parsing' into 'master'

Throw error if yaml policy has unparseable attributes for Rule mode

See merge request gitlab-org/gitlab!58457
parents fa686696 667d36ca
import { s__ } from '~/locale';
export const EditorModeRule = 'rule'; export const EditorModeRule = 'rule';
export const EditorModeYAML = 'yaml'; export const EditorModeYAML = 'yaml';
...@@ -35,3 +37,7 @@ export const DisabledByLabel = 'network-policy.gitlab.com/disabled_by'; ...@@ -35,3 +37,7 @@ export const DisabledByLabel = 'network-policy.gitlab.com/disabled_by';
export const CiliumNetworkPolicyKind = 'CiliumNetworkPolicy'; export const CiliumNetworkPolicyKind = 'CiliumNetworkPolicy';
export const ProjectIdLabel = 'app.gitlab.com/proj'; export const ProjectIdLabel = 'app.gitlab.com/proj';
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.',
);
...@@ -106,13 +106,87 @@ function parseRule(item, direction) { ...@@ -106,13 +106,87 @@ function parseRule(item, direction) {
}; };
} }
/**
* Checks for parameters unsupported by the network policy "Rule Mode"
* @param {String} manifest YAML of network policy
* @returns {Boolean} whether the YAML is valid to be parsed into "Rule Mode"
*/
const hasUnsupportedAttribute = (manifest) => {
const primaryKeys = ['apiVersion', 'description', 'kind', 'metadata', 'spec'];
const metadataKeys = ['annotations', 'labels', 'name', 'namespace', 'resourceVersion'];
const specKeys = ['egress', 'endpointSelector', 'ingress'];
const ruleKeys = [
'fromEntities',
'toEntities',
'fromCIDR',
'toCIDR',
'toFQDNs',
'fromEndpoints',
'toEndpoints',
'toPorts',
];
const toPortKeys = ['ports'];
const portKeys = ['port', 'protocol'];
let isUnsupported = false;
const hasInvalidKey = (object, allowedValues) => {
return !Object.keys(object).every((item) => allowedValues.includes(item));
};
const hasInvalidPolicy = (ingress = [], egress = []) => {
let isInvalidPolicy = false;
[...ingress, ...egress].forEach((item) => {
isInvalidPolicy = hasInvalidKey(item, ruleKeys);
if (item.toPorts?.length && !isInvalidPolicy) {
item.toPorts.forEach((entry) => {
isInvalidPolicy = hasInvalidKey(entry, toPortKeys);
if (entry.ports?.length && !isInvalidPolicy) {
entry.ports.forEach((portEntry) => {
isInvalidPolicy = hasInvalidKey(portEntry, portKeys);
});
}
});
}
});
return isInvalidPolicy;
};
isUnsupported = hasInvalidKey(manifest, primaryKeys);
if (manifest?.metadata && !isUnsupported) {
isUnsupported = hasInvalidKey(manifest.metadata, metadataKeys);
}
if (manifest?.spec && !isUnsupported) {
isUnsupported = hasInvalidKey(manifest.spec, specKeys);
}
if (!isUnsupported && (manifest?.spec?.ingress?.length || manifest?.spec?.egress?.length)) {
isUnsupported = hasInvalidPolicy(manifest.spec.ingress, manifest.spec.egress);
}
return isUnsupported;
};
/**
* 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', '');
};
/* /*
Construct a policy object expected by the policy editor from a yaml manifest. Construct a policy object expected by the policy editor from a yaml manifest.
Expected yaml structure is defined in the official documentation: Expected yaml structure is defined in the official documentation:
https://docs.cilium.io/en/v1.8/policy/language https://docs.cilium.io/en/v1.8/policy/language
*/ */
export default function fromYaml(manifest) { export default function fromYaml(manifest) {
const { description, metadata, spec } = safeLoad(manifest, { json: true }); const policy = safeLoad(manifest, { json: true });
const unsupportedAttribute = hasUnsupportedAttribute(policy);
if (unsupportedAttribute) return { error: unsupportedAttribute };
const { description, metadata, spec } = policy;
const { name, resourceVersion, annotations, labels } = metadata; const { name, resourceVersion, annotations, labels } = metadata;
const { endpointSelector = {}, ingress = [], egress = [] } = spec; const { endpointSelector = {}, ingress = [], egress = [] } = spec;
const matchLabels = endpointSelector.matchLabels || {}; const matchLabels = endpointSelector.matchLabels || {};
......
<script> <script>
import { GlFormTextarea } from '@gitlab/ui'; import { GlFormTextarea } from '@gitlab/ui';
import fromYaml from './lib/from_yaml'; import fromYaml, { removeUnnecessaryDashes } from './lib/from_yaml';
import humanizeNetworkPolicy from './lib/humanize'; import humanizeNetworkPolicy from './lib/humanize';
import toYaml from './lib/to_yaml'; import toYaml from './lib/to_yaml';
import PolicyPreview from './policy_preview.vue'; import PolicyPreview from './policy_preview.vue';
...@@ -17,14 +17,18 @@ export default { ...@@ -17,14 +17,18 @@ export default {
}, },
}, },
computed: { computed: {
initialTab() {
return this.policy ? 1 : 0;
},
policy() { policy() {
return fromYaml(this.value); const policy = fromYaml(this.value);
return policy.error ? null : policy;
}, },
humanizedPolicy() { humanizedPolicy() {
return humanizeNetworkPolicy(this.policy); return this.policy ? humanizeNetworkPolicy(this.policy) : this.policy;
}, },
policyYaml() { policyYaml() {
return toYaml(this.policy); return removeUnnecessaryDashes(this.value);
}, },
}, },
methods: { methods: {
...@@ -43,12 +47,14 @@ export default { ...@@ -43,12 +47,14 @@ export default {
<h5 class="gl-mt-6">{{ s__('NetworkPolicies|Policy type') }}</h5> <h5 class="gl-mt-6">{{ s__('NetworkPolicies|Policy type') }}</h5>
<p>{{ s__('NetworkPolicies|Network Policy') }}</p> <p>{{ s__('NetworkPolicies|Network Policy') }}</p>
<h5 class="gl-mt-6">{{ s__('NetworkPolicies|Description') }}</h5> <div v-if="policy">
<gl-form-textarea :value="policy.description" @input="updateManifest" /> <h5 class="gl-mt-6">{{ s__('NetworkPolicies|Description') }}</h5>
<gl-form-textarea :value="policy.description" @input="updateManifest" />
</div>
<policy-preview <policy-preview
class="gl-mt-4" class="gl-mt-4"
:initial-tab="1" :initial-tab="initialTab"
:policy-yaml="policyYaml" :policy-yaml="policyYaml"
:policy-description="humanizedPolicy" :policy-description="humanizedPolicy"
/> />
......
...@@ -22,9 +22,10 @@ import { ...@@ -22,9 +22,10 @@ import {
EndpointMatchModeAny, EndpointMatchModeAny,
RuleTypeEndpoint, RuleTypeEndpoint,
ProjectIdLabel, ProjectIdLabel,
PARSING_ERROR_MESSAGE,
} from './constants'; } from './constants';
import DimDisableContainer from './dim_disable_container.vue'; import DimDisableContainer from './dim_disable_container.vue';
import fromYaml from './lib/from_yaml'; import fromYaml, { removeUnnecessaryDashes } from './lib/from_yaml';
import humanizeNetworkPolicy from './lib/humanize'; import humanizeNetworkPolicy from './lib/humanize';
import { buildRule } from './lib/rules'; import { buildRule } from './lib/rules';
import toYaml from './lib/to_yaml'; import toYaml from './lib/to_yaml';
...@@ -36,6 +37,7 @@ import PolicyRuleBuilder from './policy_rule_builder.vue'; ...@@ -36,6 +37,7 @@ import PolicyRuleBuilder from './policy_rule_builder.vue';
export default { export default {
i18n: { i18n: {
toggleLabel: s__('NetworkPolicies|Policy status'), toggleLabel: s__('NetworkPolicies|Policy status'),
PARSING_ERROR_MESSAGE,
}, },
components: { components: {
GlFormGroup, GlFormGroup,
...@@ -87,22 +89,27 @@ export default { ...@@ -87,22 +89,27 @@ export default {
labels: '', labels: '',
}; };
policy.labels = { [ProjectIdLabel]: this.projectId }; policy.labels = { [ProjectIdLabel]: this.projectId };
const yamlEditorValue = this.existingPolicy
? removeUnnecessaryDashes(this.existingPolicy.manifest)
: '';
return { return {
editorMode: EditorModeRule, editorMode: EditorModeRule,
yamlEditorValue: '', yamlEditorValue,
yamlEditorError: null, yamlEditorError: policy.error ? true : null,
policy, policy,
}; };
}, },
computed: { computed: {
humanizedPolicy() { humanizedPolicy() {
return humanizeNetworkPolicy(this.policy); return this.policy.error ? null : humanizeNetworkPolicy(this.policy);
}, },
policyAlert() { policyAlert() {
return Boolean(this.policy.annotations); return Boolean(this.policy.annotations);
}, },
policyYaml() { policyYaml() {
return toYaml(this.policy); return this.hasParsingError ? '' : toYaml(this.policy);
}, },
...mapState('threatMonitoring', ['currentEnvironmentId']), ...mapState('threatMonitoring', ['currentEnvironmentId']),
...mapState('networkPolicies', [ ...mapState('networkPolicies', [
...@@ -165,7 +172,11 @@ export default { ...@@ -165,7 +172,11 @@ export default {
this.yamlEditorError = null; this.yamlEditorError = null;
try { try {
Object.assign(this.policy, fromYaml(manifest)); const newPolicy = fromYaml(manifest);
if (newPolicy.error) {
throw new Error(newPolicy.error);
}
Object.assign(this.policy, newPolicy);
} catch (error) { } catch (error) {
this.yamlEditorError = error; this.yamlEditorError = error;
} }
...@@ -203,9 +214,6 @@ export default { ...@@ -203,9 +214,6 @@ export default {
{ value: EditorModeRule, text: s__('NetworkPolicies|Rule mode') }, { value: EditorModeRule, text: s__('NetworkPolicies|Rule mode') },
{ value: EditorModeYAML, text: s__('NetworkPolicies|.yaml mode') }, { value: EditorModeYAML, text: s__('NetworkPolicies|.yaml mode') },
], ],
parsingErrorMessage: s__(
'NetworkPolicies|Rule mode is unavailable for this policy. In some cases, we cannot parse the YAML file back into the rules editor.',
),
deleteModal: { deleteModal: {
id: 'delete-modal', id: 'delete-modal',
secondary: { secondary: {
...@@ -240,14 +248,18 @@ export default { ...@@ -240,14 +248,18 @@ export default {
</div> </div>
<div class="col-sm-6 col-md-6 col-lg-5 col-xl-4"> <div class="col-sm-6 col-md-6 col-lg-5 col-xl-4">
<gl-form-group :label="s__('NetworkPolicies|Name')" label-for="policyName"> <gl-form-group :label="s__('NetworkPolicies|Name')" label-for="policyName">
<gl-form-input id="policyName" v-model="policy.name" /> <gl-form-input id="policyName" v-model="policy.name" :disabled="hasParsingError" />
</gl-form-group> </gl-form-group>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-sm-12 col-md-10 col-lg-8 col-xl-6"> <div class="col-sm-12 col-md-10 col-lg-8 col-xl-6">
<gl-form-group :label="s__('NetworkPolicies|Description')" label-for="policyDescription"> <gl-form-group :label="s__('NetworkPolicies|Description')" label-for="policyDescription">
<gl-form-textarea id="policyDescription" v-model="policy.description" /> <gl-form-textarea
id="policyDescription"
v-model="policy.description"
:disabled="hasParsingError"
/>
</gl-form-group> </gl-form-group>
</div> </div>
</div> </div>
...@@ -256,7 +268,7 @@ export default { ...@@ -256,7 +268,7 @@ export default {
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-auto"> <div class="col-md-auto">
<gl-form-group> <gl-form-group :disabled="hasParsingError" data-testid="policy-enable">
<gl-toggle v-model="policy.isEnabled" :label="$options.i18n.toggleLabel" /> <gl-toggle v-model="policy.isEnabled" :label="$options.i18n.toggleLabel" />
</gl-form-group> </gl-form-group>
</div> </div>
...@@ -281,7 +293,7 @@ export default { ...@@ -281,7 +293,7 @@ export default {
class="gl-z-index-1" class="gl-z-index-1"
data-testid="parsing-alert" data-testid="parsing-alert"
:dismissible="false" :dismissible="false"
>{{ $options.parsingErrorMessage }}</gl-alert >{{ $options.i18n.PARSING_ERROR_MESSAGE }}</gl-alert
> >
<dim-disable-container data-testid="rule-builder-container" :disabled="hasParsingError"> <dim-disable-container data-testid="rule-builder-container" :disabled="hasParsingError">
......
<script> <script>
import { GlTabs, GlTab, GlSafeHtmlDirective } from '@gitlab/ui'; import { GlAlert, GlTabs, GlTab, GlSafeHtmlDirective } from '@gitlab/ui';
import { PARSING_ERROR_MESSAGE } from './constants';
export default { export default {
i18n: {
PARSING_ERROR_MESSAGE,
},
components: { components: {
GlAlert,
GlTabs, GlTabs,
GlTab, GlTab,
}, },
...@@ -16,7 +21,8 @@ export default { ...@@ -16,7 +21,8 @@ export default {
}, },
policyDescription: { policyDescription: {
type: String, type: String,
required: true, required: false,
default: '',
}, },
initialTab: { initialTab: {
type: Number, type: Number,
...@@ -40,9 +46,15 @@ export default { ...@@ -40,9 +46,15 @@ export default {
</gl-tab> </gl-tab>
<gl-tab :title="s__('NetworkPolicies|Rule')"> <gl-tab :title="s__('NetworkPolicies|Rule')">
<div <div
v-if="policyDescription"
v-safe-html:[$options.safeHtmlConfig]="policyDescription" v-safe-html:[$options.safeHtmlConfig]="policyDescription"
class="gl-bg-white gl-rounded-top-left-none gl-rounded-top-right-none gl-rounded-bottom-left-base gl-rounded-bottom-right-base gl-py-3 gl-px-4 gl-border-1 gl-border-solid gl-border-gray-100" class="gl-bg-white gl-rounded-top-left-none gl-rounded-top-right-none gl-rounded-bottom-left-base gl-rounded-bottom-right-base gl-py-3 gl-px-4 gl-border-1 gl-border-solid gl-border-gray-100"
></div> ></div>
<div v-else>
<gl-alert variant="info" :dismissible="false"
>{{ $options.i18n.PARSING_ERROR_MESSAGE }}
</gl-alert>
</div>
</gl-tab> </gl-tab>
</gl-tabs> </gl-tabs>
</template> </template>
---
title: Throw an error when yaml mode contains unparsable attributes
merge_request: 58457
author:
type: fixed
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PolicyDrawer component renders policy preview tabs 1`] = ` exports[`PolicyDrawer component supported YAML renders policy preview tabs 1`] = `
<div> <div>
<h4> <h4>
Policy description Policy description
...@@ -16,16 +16,18 @@ exports[`PolicyDrawer component renders policy preview tabs 1`] = ` ...@@ -16,16 +16,18 @@ exports[`PolicyDrawer component renders policy preview tabs 1`] = `
Network Policy Network Policy
</p> </p>
<h5 <div>
class="gl-mt-6" <h5
> class="gl-mt-6"
Description >
</h5> Description
</h5>
<gl-form-textarea-stub
noresize="true" <gl-form-textarea-stub
value="test description" noresize="true"
/> value="test description"
/>
</div>
<policy-preview-stub <policy-preview-stub
class="gl-mt-4" class="gl-mt-4"
...@@ -44,3 +46,29 @@ spec: ...@@ -44,3 +46,29 @@ spec:
/> />
</div> </div>
`; `;
exports[`PolicyDrawer component unsupported YAML renders policy preview tabs 1`] = `
<div>
<h4>
Policy description
</h4>
<h5
class="gl-mt-6"
>
Policy type
</h5>
<p>
Network Policy
</p>
<!---->
<policy-preview-stub
class="gl-mt-4"
initialtab="0"
policyyaml="unsupportedPrimaryKey: test"
/>
</div>
`;
...@@ -111,7 +111,9 @@ exports[`PolicyEditorApp component renders the policy editor layout 1`] = ` ...@@ -111,7 +111,9 @@ exports[`PolicyEditorApp component renders the policy editor layout 1`] = `
<div <div
class="col-md-auto" class="col-md-auto"
> >
<gl-form-group-stub> <gl-form-group-stub
data-testid="policy-enable"
>
<gl-toggle-stub <gl-toggle-stub
label="Policy status" label="Policy status"
labelposition="top" labelposition="top"
......
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PolicyPreview component renders policy preview tabs 1`] = ` exports[`PolicyPreview component with policy description renders policy preview tabs 1`] = `
<gl-tabs-stub <gl-tabs-stub
contentclass="gl-pt-0" contentclass="gl-pt-0"
theme="indigo" theme="indigo"
......
...@@ -11,7 +11,9 @@ import { ...@@ -11,7 +11,9 @@ import {
RuleTypeFQDN, RuleTypeFQDN,
EntityTypes, EntityTypes,
} from 'ee/threat_monitoring/components/policy_editor/constants'; } from 'ee/threat_monitoring/components/policy_editor/constants';
import fromYaml from 'ee/threat_monitoring/components/policy_editor/lib/from_yaml'; import fromYaml, {
removeUnnecessaryDashes,
} from 'ee/threat_monitoring/components/policy_editor/lib/from_yaml';
import { buildRule } from 'ee/threat_monitoring/components/policy_editor/lib/rules'; import { buildRule } from 'ee/threat_monitoring/components/policy_editor/lib/rules';
import toYaml from 'ee/threat_monitoring/components/policy_editor/lib/to_yaml'; import toYaml from 'ee/threat_monitoring/components/policy_editor/lib/to_yaml';
...@@ -310,4 +312,36 @@ spec: ...@@ -310,4 +312,36 @@ spec:
}); });
}); });
}); });
describe('unsupported attributes', () => {
const unsupportedYaml = [
'unsupportedPrimaryKey: test',
'apiVersion: 1\nunsupportedPrimaryKey: test',
'unsupportedPrimaryKey: test\nkind: test',
'metadata:\n unsupportedMetaKey: test',
'spec:\n unsupportedSpecKey: test',
'spec:\n ingress:\n - unsupportedRuleKey: test',
'spec:\n engress:\n - toPorts:\n - unsupportedToPortKey: test',
'spec:\n ingress:\n - toPorts:\n - ports:\n - port: 80\n unsupportedPortKey: test',
];
it.each(unsupportedYaml)(
'returns the unsupported attributes object for YAML with unsupported attributes',
(manifest) => {
expect(fromYaml(manifest)).toStrictEqual({ error: true });
},
);
});
});
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);
});
}); });
...@@ -3,6 +3,7 @@ import { shallowMount } from '@vue/test-utils'; ...@@ -3,6 +3,7 @@ import { shallowMount } from '@vue/test-utils';
import fromYaml from 'ee/threat_monitoring/components/policy_editor/lib/from_yaml'; import fromYaml from 'ee/threat_monitoring/components/policy_editor/lib/from_yaml';
import toYaml from 'ee/threat_monitoring/components/policy_editor/lib/to_yaml'; import toYaml from 'ee/threat_monitoring/components/policy_editor/lib/to_yaml';
import PolicyDrawer from 'ee/threat_monitoring/components/policy_editor/policy_drawer.vue'; import PolicyDrawer from 'ee/threat_monitoring/components/policy_editor/policy_drawer.vue';
import PolicyPreview from 'ee/threat_monitoring/components/policy_editor/policy_preview.vue';
describe('PolicyDrawer component', () => { describe('PolicyDrawer component', () => {
let wrapper; let wrapper;
...@@ -12,6 +13,10 @@ describe('PolicyDrawer component', () => { ...@@ -12,6 +13,10 @@ describe('PolicyDrawer component', () => {
endpointLabels: '', endpointLabels: '',
rules: [], rules: [],
}; };
const unsupportedYaml = 'unsupportedPrimaryKey: test';
const findPolicyPreview = () => wrapper.findComponent(PolicyPreview);
const findTextForm = () => wrapper.findComponent(GlFormTextarea);
const factory = ({ propsData } = {}) => { const factory = ({ propsData } = {}) => {
wrapper = shallowMount(PolicyDrawer, { wrapper = shallowMount(PolicyDrawer, {
...@@ -21,27 +26,62 @@ describe('PolicyDrawer component', () => { ...@@ -21,27 +26,62 @@ describe('PolicyDrawer component', () => {
}); });
}; };
beforeEach(() => {
factory({
propsData: {
value: toYaml(policy),
},
});
});
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
}); });
it('renders policy preview tabs', () => { describe('supported YAML', () => {
expect(wrapper.find('div').element).toMatchSnapshot(); beforeEach(() => {
factory({ propsData: { value: toYaml(policy) } });
});
it('renders policy preview tabs', () => {
expect(wrapper.find('div').element).toMatchSnapshot();
});
it('does render the policy description', () => {
expect(findTextForm().exists()).toBe(true);
expect(findTextForm().props()).toMatchObject({ value: 'test description' });
});
it('does render the policy preview', () => {
expect(findPolicyPreview().exists()).toBe(true);
expect(findPolicyPreview().props()).toStrictEqual({
initialTab: 1,
policyDescription: 'Deny all traffic',
policyYaml: toYaml(policy),
});
});
it('emits input event on description change', () => {
wrapper.find(GlFormTextarea).vm.$emit('input', 'new description');
expect(wrapper.emitted().input.length).toEqual(1);
const updatedPolicy = fromYaml(wrapper.emitted().input[0][0]);
expect(updatedPolicy.description).toEqual('new description');
});
}); });
it('emits input event on description change', () => { describe('unsupported YAML', () => {
wrapper.find(GlFormTextarea).vm.$emit('input', 'new description'); beforeEach(() => {
factory({ propsData: { value: unsupportedYaml } });
});
expect(wrapper.emitted().input.length).toEqual(1); it('renders policy preview tabs', () => {
const updatedPolicy = fromYaml(wrapper.emitted().input[0][0]); expect(wrapper.find('div').element).toMatchSnapshot();
expect(updatedPolicy.description).toEqual('new description'); });
it('does not render the policy description', () => {
expect(findTextForm().exists()).toBe(false);
});
it('does render the policy preview', () => {
expect(findPolicyPreview().exists()).toBe(true);
expect(findPolicyPreview().props()).toStrictEqual({
initialTab: 0,
policyDescription: null,
policyYaml: unsupportedYaml,
});
});
}); });
}); });
...@@ -15,6 +15,7 @@ import PolicyEditorApp from 'ee/threat_monitoring/components/policy_editor/polic ...@@ -15,6 +15,7 @@ import PolicyEditorApp from 'ee/threat_monitoring/components/policy_editor/polic
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 PolicyRuleBuilder from 'ee/threat_monitoring/components/policy_editor/policy_rule_builder.vue'; import PolicyRuleBuilder from 'ee/threat_monitoring/components/policy_editor/policy_rule_builder.vue';
import createStore from 'ee/threat_monitoring/store'; import createStore from 'ee/threat_monitoring/store';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { redirectTo } from '~/lib/utils/url_utility'; import { redirectTo } from '~/lib/utils/url_utility';
jest.mock('~/lib/utils/url_utility'); jest.mock('~/lib/utils/url_utility');
...@@ -53,33 +54,37 @@ spec: ...@@ -53,33 +54,37 @@ spec:
jest.spyOn(store, 'dispatch').mockImplementation(() => Promise.resolve()); jest.spyOn(store, 'dispatch').mockImplementation(() => Promise.resolve());
wrapper = shallowMount(PolicyEditorApp, { wrapper = extendedWrapper(
propsData: { shallowMount(PolicyEditorApp, {
threatMonitoringPath: '/threat-monitoring', propsData: {
projectId: '21', threatMonitoringPath: '/threat-monitoring',
...propsData, projectId: '21',
}, ...propsData,
provide: { },
glFeatures: { threatMonitoringAlerts: false }, provide: {
...provide, glFeatures: { threatMonitoringAlerts: false },
}, ...provide,
store, },
data, store,
stubs: { NetworkPolicyEditor: true }, data,
}); stubs: { NetworkPolicyEditor: true },
}),
);
}; };
const findRuleEditor = () => wrapper.find('[data-testid="rule-editor"]'); const findRuleEditor = () => wrapper.findByTestId('rule-editor');
const findYamlEditor = () => wrapper.find('[data-testid="yaml-editor"]'); const findYamlEditor = () => wrapper.findByTestId('yaml-editor');
const findPreview = () => wrapper.find(PolicyPreview); const findPreview = () => wrapper.find(PolicyPreview);
const findAddRuleButton = () => wrapper.find('[data-testid="add-rule"]'); const findAddRuleButton = () => wrapper.findByTestId('add-rule');
const findYAMLParsingAlert = () => wrapper.find('[data-testid="parsing-alert"]'); const findYAMLParsingAlert = () => wrapper.findByTestId('parsing-alert');
const findNetworkPolicyEditor = () => wrapper.find('[data-testid="network-policy-editor"]'); const findNetworkPolicyEditor = () => wrapper.findByTestId('network-policy-editor');
const findPolicyAlertPicker = () => wrapper.find(PolicyAlertPicker); const findPolicyAlertPicker = () => wrapper.find(PolicyAlertPicker);
const findPolicyDescription = () => wrapper.find("[id='policyDescription']");
const findPolicyEnableContainer = () => wrapper.findByTestId('policy-enable');
const findPolicyName = () => wrapper.find("[id='policyName']"); const findPolicyName = () => wrapper.find("[id='policyName']");
const findSavePolicy = () => wrapper.find("[data-testid='save-policy']"); const findSavePolicy = () => wrapper.findByTestId('save-policy');
const findDeletePolicy = () => wrapper.find("[data-testid='delete-policy']"); const findDeletePolicy = () => wrapper.findByTestId('delete-policy');
const findEditorModeToggle = () => wrapper.find("[data-testid='editor-mode']"); const findEditorModeToggle = () => wrapper.findByTestId('editor-mode');
const modifyPolicyAlert = async ({ isAlertEnabled }) => { const modifyPolicyAlert = async ({ isAlertEnabled }) => {
const policyAlertPicker = findPolicyAlertPicker(); const policyAlertPicker = findPolicyAlertPicker();
...@@ -213,7 +218,7 @@ spec: ...@@ -213,7 +218,7 @@ spec:
beforeEach(() => { beforeEach(() => {
initialValue = findPreview().props('policyDescription'); initialValue = findPreview().props('policyDescription');
wrapper.find("[data-testid='add-rule']").vm.$emit('click'); wrapper.findByTestId('add-rule').vm.$emit('click');
}); });
it('updates policy description preview', () => { it('updates policy description preview', () => {
...@@ -273,20 +278,32 @@ spec: ...@@ -273,20 +278,32 @@ spec:
}); });
}); });
it('disables policy name field', () => {
expect(findPolicyName().attributes().disabled).toBe('true');
});
it('disables policy description field', () => {
expect(findPolicyDescription().attributes().disabled).toBe('true');
});
it('disables policy enable/disable toggle', () => {
expect(findPolicyEnableContainer().attributes().disabled).toBe('true');
});
it('renders parsing error alert', () => { it('renders parsing error alert', () => {
expect(findYAMLParsingAlert().exists()).toBe(true); expect(findYAMLParsingAlert().exists()).toBe(true);
}); });
it('disables rule builder', () => { it('disables rule builder', () => {
expect(wrapper.find("[data-testid='rule-builder-container']").props().disabled).toBe(true); expect(wrapper.findByTestId('rule-builder-container').props().disabled).toBe(true);
}); });
it('disables action picker', () => { it('disables action picker', () => {
expect(wrapper.find("[data-testid='policy-action-container']").props().disabled).toBe(true); expect(wrapper.findByTestId('policy-action-container').props().disabled).toBe(true);
}); });
it('disables policy preview', () => { it('disables policy preview', () => {
expect(wrapper.find("[data-testid='policy-preview-container']").props().disabled).toBe(true); expect(wrapper.findByTestId('policy-preview-container').props().disabled).toBe(true);
}); });
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 () => {
......
import { GlTabs } from '@gitlab/ui'; import { GlAlert, GlTabs } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import PolicyPreview from 'ee/threat_monitoring/components/policy_editor/policy_preview.vue'; import PolicyPreview from 'ee/threat_monitoring/components/policy_editor/policy_preview.vue';
describe('PolicyPreview component', () => { describe('PolicyPreview component', () => {
let wrapper; let wrapper;
const findAlert = () => wrapper.findComponent(GlAlert);
const findTabs = () => wrapper.findComponent(GlTabs);
const factory = ({ propsData } = {}) => { const factory = ({ propsData } = {}) => {
wrapper = shallowMount(PolicyPreview, { wrapper = shallowMount(PolicyPreview, {
propsData: { propsData: {
...@@ -13,36 +16,57 @@ describe('PolicyPreview component', () => { ...@@ -13,36 +16,57 @@ describe('PolicyPreview component', () => {
}); });
}; };
beforeEach(() => {
factory({
propsData: {
policyYaml: 'foo',
policyDescription: '<strong>bar</strong><br><div>test</div><script></script>',
},
});
});
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
}); });
it('renders policy preview tabs', () => { describe('with policy description', () => {
expect(wrapper.find(GlTabs).element).toMatchSnapshot(); beforeEach(() => {
factory({
propsData: {
policyYaml: 'foo',
policyDescription: '<strong>bar</strong><br><div>test</div><script></script>',
},
});
});
it('renders policy preview tabs', () => {
expect(findTabs().element).toMatchSnapshot();
});
it('renders the first tab', () => {
expect(findTabs().attributes().value).toEqual('0');
});
it('does not render the unsupported attributes alert', () => {
expect(findAlert().exists()).toBe(false);
});
describe('initial tab', () => {
it('selects initial tab', () => {
factory({
propsData: {
policyYaml: 'foo',
policyDescription: 'bar',
initialTab: 1,
},
});
expect(findTabs().attributes().value).toEqual('1');
});
});
}); });
describe('with initialTab', () => { describe('without policy description', () => {
beforeEach(() => { beforeEach(() => {
factory({ factory({
propsData: { propsData: {
policyYaml: 'foo', policyYaml: 'foo',
policyDescription: 'bar',
initialTab: 1,
}, },
}); });
}); });
it('selects initial tab', () => { it('does render the unsupported attributes alert', () => {
expect(wrapper.find(GlTabs).attributes().value).toEqual('1'); expect(findAlert().exists()).toBe(true);
}); });
}); });
}); });
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