Commit a0bb94ba authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'add_validation_for_invalid_branches_scan_policies' into 'master'

Add validation for invalid protected branches for scan result policies

See merge request gitlab-org/gitlab!84107
parents 460fca55 d79660fe
...@@ -42,6 +42,7 @@ const Api = { ...@@ -42,6 +42,7 @@ const Api = {
projectMergeRequestVersionsPath: '/api/:version/projects/:id/merge_requests/:mrid/versions', projectMergeRequestVersionsPath: '/api/:version/projects/:id/merge_requests/:mrid/versions',
projectRunnersPath: '/api/:version/projects/:id/runners', projectRunnersPath: '/api/:version/projects/:id/runners',
projectProtectedBranchesPath: '/api/:version/projects/:id/protected_branches', projectProtectedBranchesPath: '/api/:version/projects/:id/protected_branches',
projectProtectedBranchesNamePath: '/api/:version/projects/:id/protected_branches/:name',
projectSearchPath: '/api/:version/projects/:id/search', projectSearchPath: '/api/:version/projects/:id/search',
projectSharePath: '/api/:version/projects/:id/share', projectSharePath: '/api/:version/projects/:id/share',
projectMilestonesPath: '/api/:version/projects/:id/milestones', projectMilestonesPath: '/api/:version/projects/:id/milestones',
...@@ -372,6 +373,14 @@ const Api = { ...@@ -372,6 +373,14 @@ const Api = {
.then(({ data }) => data); .then(({ data }) => data);
}, },
projectProtectedBranch(id, branchName) {
const url = Api.buildUrl(Api.projectProtectedBranchesNamePath)
.replace(':id', encodeURIComponent(id))
.replace(':name', branchName);
return axios.get(url).then(({ data }) => data);
},
projectSearch(id, options = {}) { projectSearch(id, options = {}) {
const url = Api.buildUrl(Api.projectSearchPath).replace(':id', encodeURIComponent(id)); const url = Api.buildUrl(Api.projectSearchPath).replace(':id', encodeURIComponent(id));
......
...@@ -36,3 +36,7 @@ export const GRAPHQL_ERROR_MESSAGE = s__( ...@@ -36,3 +36,7 @@ export const GRAPHQL_ERROR_MESSAGE = s__(
); );
export const NO_RULE_MESSAGE = s__('SecurityOrchestration|No rules defined - policy will not run.'); export const NO_RULE_MESSAGE = s__('SecurityOrchestration|No rules defined - policy will not run.');
export const INVALID_BRANCHES = s__(
'SecurityOrchestration|The following branches do not exist on this development project: %{branches}. Please review all branches to ensure the values are accurate before updating this policy.',
);
import { sprintf, s__, n__ } from '~/locale'; import { sprintf, s__, n__ } from '~/locale';
import { NO_RULE_MESSAGE } from '../../constants'; import { NO_RULE_MESSAGE, INVALID_BRANCHES } from '../../constants';
import { convertScannersToTitleCase } from '../../utils'; import { convertScannersToTitleCase } from '../../utils';
/** /**
...@@ -53,9 +53,7 @@ const humanizeItems = ({ ...@@ -53,9 +53,7 @@ const humanizeItems = ({
finalSentence.push(items.join(',')); finalSentence.push(items.join(','));
} else { } else {
const lastItem = items.pop(); const lastItem = items.pop();
finalSentence.push(items.join(', ')); finalSentence.push(items.join(', '), s__('SecurityOrchestration| or '), lastItem);
finalSentence.push(s__('SecurityOrchestration| or '));
finalSentence.push(lastItem);
} }
if (!hasTextBeforeItems && noun) { if (!hasTextBeforeItems && noun) {
...@@ -215,3 +213,14 @@ export const humanizeRules = (rules) => { ...@@ -215,3 +213,14 @@ export const humanizeRules = (rules) => {
}, []); }, []);
return humanizedRules.length ? humanizedRules : [NO_RULE_MESSAGE]; return humanizedRules.length ? humanizedRules : [NO_RULE_MESSAGE];
}; };
export const humanizeInvalidBranchesError = (branches) => {
const sentence = [];
if (branches.length > 1) {
const lastBranch = branches.pop();
sentence.push(branches.join(', '), s__('SecurityOrchestration| and '), lastBranch);
} else {
sentence.push(branches.join());
}
return sprintf(INVALID_BRANCHES, { branches: sentence.join('') });
};
...@@ -10,8 +10,7 @@ description: '' ...@@ -10,8 +10,7 @@ description: ''
enabled: false enabled: false
rules: rules:
- type: scan_finding - type: scan_finding
branches: branches: []
- main
scanners: scanners:
- container_scanning - container_scanning
vulnerabilities_allowed: 0 vulnerabilities_allowed: 0
......
...@@ -8,6 +8,7 @@ import { ...@@ -8,6 +8,7 @@ import {
GlFormTextarea, GlFormTextarea,
GlAlert, GlAlert,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { mapActions, mapState } from 'vuex';
import { joinPaths, visitUrl, setUrlFragment } from '~/lib/utils/url_utility'; import { joinPaths, visitUrl, setUrlFragment } from '~/lib/utils/url_utility';
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
import { import {
...@@ -29,6 +30,7 @@ import { ...@@ -29,6 +30,7 @@ import {
buildRule, buildRule,
approversOutOfSync, approversOutOfSync,
invalidScanners, invalidScanners,
humanizeInvalidBranchesError,
} from './lib'; } from './lib';
export default { export default {
...@@ -111,6 +113,7 @@ export default { ...@@ -111,6 +113,7 @@ export default {
}; };
}, },
computed: { computed: {
...mapState('scanResultPolicies', ['invalidBranches']),
originalName() { originalName() {
return this.existingPolicy?.name; return this.existingPolicy?.name;
}, },
...@@ -129,7 +132,17 @@ export default { ...@@ -129,7 +132,17 @@ export default {
return this.policy.rules.length < 5; return this.policy.rules.length < 5;
}, },
}, },
watch: {
invalidBranches(branches) {
if (branches.length > 0) {
this.handleError(new Error(humanizeInvalidBranchesError([...branches])));
} else {
this.$emit('error', '');
}
},
},
methods: { methods: {
...mapActions('scanResultPolicies', ['fetchBranches']),
updateAction(actionIndex, values) { updateAction(actionIndex, values) {
this.policy.actions.splice(actionIndex, 1, values); this.policy.actions.splice(actionIndex, 1, values);
}, },
...@@ -218,6 +231,8 @@ export default { ...@@ -218,6 +231,8 @@ export default {
} else if (mode === EDITOR_MODE_RULE && !this.hasParsingError) { } else if (mode === EDITOR_MODE_RULE && !this.hasParsingError) {
if (this.invalidForRuleMode()) { if (this.invalidForRuleMode()) {
this.yamlEditorError = new Error(); this.yamlEditorError = new Error();
} else {
this.fetchBranches({ branches: this.allBranches(), projectId: this.projectId });
} }
} }
}, },
...@@ -230,6 +245,9 @@ export default { ...@@ -230,6 +245,9 @@ export default {
invalidScanners(this.policy.rules) invalidScanners(this.policy.rules)
); );
}, },
allBranches() {
return this.policy.rules.flatMap((rule) => rule.branches);
},
}, },
}; };
</script> </script>
......
...@@ -4,6 +4,7 @@ import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; ...@@ -4,6 +4,7 @@ import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import networkPolicies from './modules/network_policies'; import networkPolicies from './modules/network_policies';
import threatMonitoring from './modules/threat_monitoring'; import threatMonitoring from './modules/threat_monitoring';
import threatMonitoringStatistics from './modules/threat_monitoring_statistics'; import threatMonitoringStatistics from './modules/threat_monitoring_statistics';
import scanResultPolicies from './modules/scan_result_policies';
Vue.use(Vuex); Vue.use(Vuex);
...@@ -28,5 +29,6 @@ export default () => ...@@ -28,5 +29,6 @@ export default () =>
}; };
}), }),
networkPolicies: networkPolicies(), networkPolicies: networkPolicies(),
scanResultPolicies: scanResultPolicies(),
}, },
}); });
import Api from 'ee/api';
import * as types from './mutation_types';
export const fetchBranches = ({ commit, dispatch }, { branches, projectId }) => {
const uniqBranches = branches.filter((value, index, self) => self.indexOf(value) === index);
commit(types.LOADING_BRANCHES);
uniqBranches.forEach((branch) => dispatch('fetchBranch', { branch, projectId }));
};
export const fetchBranch = ({ commit }, { branch, projectId }) => {
return Api.projectProtectedBranch(projectId, branch).catch((error) => {
const decomposedUrl = error.config.url.split('/');
commit(types.INVALID_BRANCHES, decomposedUrl[decomposedUrl.length - 1]);
});
};
import * as actions from './actions';
import mutations from './mutations';
import state from './state';
export default () => ({
namespaced: true,
actions,
mutations,
state,
});
export const LOADING_BRANCHES = 'LOADING_BRANCHES';
export const INVALID_BRANCHES = 'INVALID_BRANCHES';
import * as types from './mutation_types';
export default {
[types.LOADING_BRANCHES](state) {
state.invalidBranches = [];
},
[types.INVALID_BRANCHES](state, invalidBranch) {
state.invalidBranches.push(invalidBranch);
},
};
import { import {
humanizeRules, humanizeRules,
humanizeAction, humanizeAction,
humanizeInvalidBranchesError,
} from 'ee/threat_monitoring/components/policy_editor/scan_result_policy/lib'; } from 'ee/threat_monitoring/components/policy_editor/scan_result_policy/lib';
import { NO_RULE_MESSAGE } from 'ee/threat_monitoring/components/policy_editor/constants'; import { NO_RULE_MESSAGE } from 'ee/threat_monitoring/components/policy_editor/constants';
...@@ -138,4 +139,24 @@ describe('humanizeAction', () => { ...@@ -138,4 +139,24 @@ describe('humanizeAction', () => {
'Require 2 approvals from o.leticia.conner or user with id 5 or members of the group security_group/all_members or members of the group with id 10 if any of the following occur:', 'Require 2 approvals from o.leticia.conner or user with id 5 or members of the group security_group/all_members or members of the group with id 10 if any of the following occur:',
); );
}); });
describe('humanizeInvalidBranchesError', () => {
it('returns message without any branch name for an empty array', () => {
expect(humanizeInvalidBranchesError([])).toEqual(
'The following branches do not exist on this development project: . Please review all branches to ensure the values are accurate before updating this policy.',
);
});
it('returns message with a single branch name for an array with single element', () => {
expect(humanizeInvalidBranchesError(['main'])).toEqual(
'The following branches do not exist on this development project: main. Please review all branches to ensure the values are accurate before updating this policy.',
);
});
it('returns message with multiple branch names for array with multiple elements', () => {
expect(humanizeInvalidBranchesError(['main', 'protected', 'master'])).toEqual(
'The following branches do not exist on this development project: main, protected and master. Please review all branches to ensure the values are accurate before updating this policy.',
);
});
});
}); });
import Vuex from 'vuex';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlEmptyState, GlAlert, GlFormInput, GlFormTextarea, GlToggle } from '@gitlab/ui'; import { GlEmptyState, GlAlert, GlFormInput, GlFormTextarea, GlToggle } from '@gitlab/ui';
import { nextTick } from 'vue'; import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import PolicyEditorLayout from 'ee/threat_monitoring/components/policy_editor/policy_editor_layout.vue'; import PolicyEditorLayout from 'ee/threat_monitoring/components/policy_editor/policy_editor_layout.vue';
import { import {
...@@ -24,6 +26,9 @@ import { ...@@ -24,6 +26,9 @@ import {
import DimDisableContainer from 'ee/threat_monitoring/components/policy_editor/dim_disable_container.vue'; import DimDisableContainer from 'ee/threat_monitoring/components/policy_editor/dim_disable_container.vue';
import PolicyActionBuilder from 'ee/threat_monitoring/components/policy_editor/scan_result_policy/policy_action_builder.vue'; import PolicyActionBuilder from 'ee/threat_monitoring/components/policy_editor/scan_result_policy/policy_action_builder.vue';
import PolicyRuleBuilder from 'ee/threat_monitoring/components/policy_editor/scan_result_policy/policy_rule_builder.vue'; import PolicyRuleBuilder from 'ee/threat_monitoring/components/policy_editor/scan_result_policy/policy_rule_builder.vue';
import ScanResultPoliciesStore from 'ee/threat_monitoring/store/modules/scan_result_policies';
import axios from '~/lib/utils/axios_utils';
import httpStatus from '~/lib/utils/http_status';
jest.mock('~/lib/utils/url_utility', () => ({ jest.mock('~/lib/utils/url_utility', () => ({
joinPaths: jest.requireActual('~/lib/utils/url_utility').joinPaths, joinPaths: jest.requireActual('~/lib/utils/url_utility').joinPaths,
...@@ -43,7 +48,10 @@ jest.mock('ee/threat_monitoring/components/policy_editor/utils', () => ({ ...@@ -43,7 +48,10 @@ jest.mock('ee/threat_monitoring/components/policy_editor/utils', () => ({
modifyPolicy: jest.fn().mockResolvedValue({ id: '2' }), modifyPolicy: jest.fn().mockResolvedValue({ id: '2' }),
})); }));
Vue.use(Vuex);
describe('ScanResultPolicyEditor', () => { describe('ScanResultPolicyEditor', () => {
let mock;
let wrapper; let wrapper;
const defaultProjectPath = 'path/to/project'; const defaultProjectPath = 'path/to/project';
const policyEditorEmptyStateSvgPath = 'path/to/svg'; const policyEditorEmptyStateSvgPath = 'path/to/svg';
...@@ -55,7 +63,14 @@ describe('ScanResultPolicyEditor', () => { ...@@ -55,7 +63,14 @@ describe('ScanResultPolicyEditor', () => {
const scanResultPolicyApprovers = [{ id: 1, username: 'the.one', state: 'active' }]; const scanResultPolicyApprovers = [{ id: 1, username: 'the.one', state: 'active' }];
const factory = ({ propsData = {}, provide = {} } = {}) => { const factory = ({ propsData = {}, provide = {} } = {}) => {
const store = new Vuex.Store({
modules: {
scanResultPolicies: ScanResultPoliciesStore(),
},
});
wrapper = shallowMount(ScanResultPolicyEditor, { wrapper = shallowMount(ScanResultPolicyEditor, {
store,
propsData: { propsData: {
assignedPolicyProject: DEFAULT_ASSIGNED_POLICY_PROJECT, assignedPolicyProject: DEFAULT_ASSIGNED_POLICY_PROJECT,
...propsData, ...propsData,
...@@ -70,7 +85,7 @@ describe('ScanResultPolicyEditor', () => { ...@@ -70,7 +85,7 @@ describe('ScanResultPolicyEditor', () => {
...provide, ...provide,
}, },
}); });
nextTick(); Vue.nextTick();
}; };
const factoryWithExistingPolicy = (policy = {}) => { const factoryWithExistingPolicy = (policy = {}) => {
...@@ -96,7 +111,12 @@ describe('ScanResultPolicyEditor', () => { ...@@ -96,7 +111,12 @@ describe('ScanResultPolicyEditor', () => {
const findYamlPreview = () => wrapper.find('[data-testid="yaml-preview"]'); const findYamlPreview = () => wrapper.find('[data-testid="yaml-preview"]');
const findAllRuleBuilders = () => wrapper.findAllComponents(PolicyRuleBuilder); const findAllRuleBuilders = () => wrapper.findAllComponents(PolicyRuleBuilder);
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => { afterEach(() => {
mock.restore();
wrapper.destroy(); wrapper.destroy();
}); });
...@@ -110,7 +130,7 @@ describe('ScanResultPolicyEditor', () => { ...@@ -110,7 +130,7 @@ describe('ScanResultPolicyEditor', () => {
); );
findPolicyEditorLayout().vm.$emit('update-yaml', newManifest); findPolicyEditorLayout().vm.$emit('update-yaml', newManifest);
await nextTick(); await Vue.nextTick();
expect(findPolicyEditorLayout().attributes('yamleditorvalue')).toBe(newManifest); expect(findPolicyEditorLayout().attributes('yamleditorvalue')).toBe(newManifest);
}); });
...@@ -128,7 +148,7 @@ describe('ScanResultPolicyEditor', () => { ...@@ -128,7 +148,7 @@ describe('ScanResultPolicyEditor', () => {
expect(findAlert().exists()).toBe(false); expect(findAlert().exists()).toBe(false);
findPolicyEditorLayout().vm.$emit('update-yaml', 'invalid manifest'); findPolicyEditorLayout().vm.$emit('update-yaml', 'invalid manifest');
await nextTick(); await Vue.nextTick();
expect(findAlert().exists()).toBe(true); expect(findAlert().exists()).toBe(true);
}); });
...@@ -137,7 +157,7 @@ describe('ScanResultPolicyEditor', () => { ...@@ -137,7 +157,7 @@ describe('ScanResultPolicyEditor', () => {
await factory(); await factory();
findPolicyEditorLayout().vm.$emit('update-yaml', 'invalid manifest'); findPolicyEditorLayout().vm.$emit('update-yaml', 'invalid manifest');
await nextTick(); await Vue.nextTick();
expect(findNameInput().attributes('disabled')).toBe('true'); expect(findNameInput().attributes('disabled')).toBe('true');
expect(findDescriptionTextArea().attributes('disabled')).toBe('true'); expect(findDescriptionTextArea().attributes('disabled')).toBe('true');
...@@ -177,7 +197,7 @@ describe('ScanResultPolicyEditor', () => { ...@@ -177,7 +197,7 @@ describe('ScanResultPolicyEditor', () => {
currentComponent().vm.$emit(event, newValue); currentComponent().vm.$emit(event, newValue);
findPolicyEditorLayout().vm.$emit('update-editor-mode', EDITOR_MODE_YAML); findPolicyEditorLayout().vm.$emit('update-editor-mode', EDITOR_MODE_YAML);
await nextTick(); await Vue.nextTick();
expect(findPolicyEditorLayout().attributes('yamleditorvalue')).toMatch(newValue.toString()); expect(findPolicyEditorLayout().attributes('yamleditorvalue')).toMatch(newValue.toString());
}); });
...@@ -186,7 +206,7 @@ describe('ScanResultPolicyEditor', () => { ...@@ -186,7 +206,7 @@ describe('ScanResultPolicyEditor', () => {
await factory(); await factory();
currentComponent().vm.$emit(event, newValue); currentComponent().vm.$emit(event, newValue);
await nextTick(); await Vue.nextTick();
expect(findYamlPreview().html()).toMatch(newValue.toString()); expect(findYamlPreview().html()).toMatch(newValue.toString());
}); });
...@@ -225,25 +245,21 @@ describe('ScanResultPolicyEditor', () => { ...@@ -225,25 +245,21 @@ describe('ScanResultPolicyEditor', () => {
it('adds a new rule', async () => { it('adds a new rule', async () => {
const rulesCount = 1; const rulesCount = 1;
factory(); factory();
await nextTick(); await Vue.nextTick();
expect(findAllRuleBuilders().length).toBe(rulesCount); expect(findAllRuleBuilders().length).toBe(rulesCount);
findAddRuleButton().vm.$emit('click'); findAddRuleButton().vm.$emit('click');
await nextTick(); await Vue.nextTick();
expect(findAllRuleBuilders()).toHaveLength(rulesCount + 1); expect(findAllRuleBuilders()).toHaveLength(rulesCount + 1);
}); });
it('hides add button when the limit of five rules has been reached', async () => { it('hides add button when the limit of five rules has been reached', async () => {
const limit = 5; const limit = 5;
factory(); const rule = mockScanResultObject.rules[0];
await nextTick(); factoryWithExistingPolicy({ rules: [rule, rule, rule, rule, rule] });
findAddRuleButton().vm.$emit('click'); await Vue.nextTick();
findAddRuleButton().vm.$emit('click');
findAddRuleButton().vm.$emit('click');
findAddRuleButton().vm.$emit('click');
await nextTick();
expect(findAllRuleBuilders()).toHaveLength(limit); expect(findAllRuleBuilders()).toHaveLength(limit);
expect(findAddRuleButton().exists()).toBe(false); expect(findAddRuleButton().exists()).toBe(false);
...@@ -259,9 +275,9 @@ describe('ScanResultPolicyEditor', () => { ...@@ -259,9 +275,9 @@ describe('ScanResultPolicyEditor', () => {
vulnerability_states: [], vulnerability_states: [],
}; };
factory(); factory();
await nextTick(); await Vue.nextTick();
findAllRuleBuilders().at(0).vm.$emit('changed', newValue); findAllRuleBuilders().at(0).vm.$emit('changed', newValue);
await nextTick(); await Vue.nextTick();
expect(wrapper.vm.policy.rules[0]).toEqual(newValue); expect(wrapper.vm.policy.rules[0]).toEqual(newValue);
expect(findYamlPreview().html()).toMatch('vulnerabilities_allowed: 1'); expect(findYamlPreview().html()).toMatch('vulnerabilities_allowed: 1');
...@@ -270,12 +286,12 @@ describe('ScanResultPolicyEditor', () => { ...@@ -270,12 +286,12 @@ describe('ScanResultPolicyEditor', () => {
it('deletes the initial rule', async () => { it('deletes the initial rule', async () => {
const initialRuleCount = 1; const initialRuleCount = 1;
factory(); factory();
await nextTick(); await Vue.nextTick();
expect(findAllRuleBuilders()).toHaveLength(initialRuleCount); expect(findAllRuleBuilders()).toHaveLength(initialRuleCount);
findAllRuleBuilders().at(0).vm.$emit('remove', 0); findAllRuleBuilders().at(0).vm.$emit('remove', 0);
await nextTick(); await Vue.nextTick();
expect(findAllRuleBuilders()).toHaveLength(initialRuleCount - 1); expect(findAllRuleBuilders()).toHaveLength(initialRuleCount - 1);
}); });
...@@ -297,7 +313,7 @@ describe('ScanResultPolicyEditor', () => { ...@@ -297,7 +313,7 @@ describe('ScanResultPolicyEditor', () => {
it('renders a single policy action builder', async () => { it('renders a single policy action builder', async () => {
factory(); factory();
await nextTick(); await Vue.nextTick();
expect(findAllPolicyActionBuilders()).toHaveLength(1); expect(findAllPolicyActionBuilders()).toHaveLength(1);
expect(findPolicyActionBuilder().props('existingApprovers')).toEqual( expect(findPolicyActionBuilder().props('existingApprovers')).toEqual(
...@@ -309,9 +325,9 @@ describe('ScanResultPolicyEditor', () => { ...@@ -309,9 +325,9 @@ describe('ScanResultPolicyEditor', () => {
const UPDATED_ACTION = { type: 'required_approval', group_approvers_ids: [1] }; const UPDATED_ACTION = { type: 'required_approval', group_approvers_ids: [1] };
factory(); factory();
await nextTick(); await Vue.nextTick();
findPolicyActionBuilder().vm.$emit('changed', UPDATED_ACTION); findPolicyActionBuilder().vm.$emit('changed', UPDATED_ACTION);
await nextTick(); await Vue.nextTick();
expect(findPolicyActionBuilder().props('initAction')).toEqual(UPDATED_ACTION); expect(findPolicyActionBuilder().props('initAction')).toEqual(UPDATED_ACTION);
}); });
...@@ -347,4 +363,25 @@ describe('ScanResultPolicyEditor', () => { ...@@ -347,4 +363,25 @@ describe('ScanResultPolicyEditor', () => {
expect(findAlert().exists()).toBe(true); expect(findAlert().exists()).toBe(true);
}); });
}); });
it.each`
status | errorMessage
${httpStatus.OK} | ${''}
${httpStatus.NOT_FOUND} | ${'The following branches do not exist on this development project: main. Please review all branches to ensure the values are accurate before updating this policy.'}
`(
'triggers error event with content: "$errorMessage" when http status is $status',
async ({ status, errorMessage }) => {
const rule = { ...mockScanResultObject.rules[0], branches: ['main'] };
mock.onGet('/api/undefined/projects/1/protected_branches/main').replyOnce(status, {});
factoryWithExistingPolicy({ rules: [rule] });
await findPolicyEditorLayout().vm.$emit('update-editor-mode', EDITOR_MODE_RULE);
await waitForPromises();
const errors = wrapper.emitted('error');
expect(errors[errors.length - 1]).toEqual([errorMessage]);
},
);
}); });
...@@ -231,8 +231,7 @@ description: This policy enforces critical vulnerability CS approvals ...@@ -231,8 +231,7 @@ description: This policy enforces critical vulnerability CS approvals
enabled: true enabled: true
rules: rules:
- type: scan_finding - type: scan_finding
branches: branches: []
- main
scanners: scanners:
- container_scanning - container_scanning
vulnerabilities_allowed: 1 vulnerabilities_allowed: 1
...@@ -255,7 +254,7 @@ export const mockScanResultObject = { ...@@ -255,7 +254,7 @@ export const mockScanResultObject = {
rules: [ rules: [
{ {
type: 'scan_finding', type: 'scan_finding',
branches: ['main'], branches: [],
scanners: ['container_scanning'], scanners: ['container_scanning'],
vulnerabilities_allowed: 1, vulnerabilities_allowed: 1,
severity_levels: ['critical'], severity_levels: ['critical'],
......
import MockAdapter from 'axios-mock-adapter';
import * as actions from 'ee/threat_monitoring/store/modules/scan_result_policies/actions';
import * as types from 'ee/threat_monitoring/store/modules/scan_result_policies/mutation_types';
import getInitialState from 'ee/threat_monitoring/store/modules/scan_result_policies/state';
import testAction from 'helpers/vuex_action_helper';
import axios from '~/lib/utils/axios_utils';
import httpStatus from '~/lib/utils/http_status';
describe('ScanResultPolicies actions', () => {
let state;
let mock;
const projectId = 3;
const branchName = 'main';
const branches = [branchName];
const duplicatedBranches = [branchName, branchName];
const apiEndpoint = `/api/undefined/projects/${projectId}/protected_branches/${branchName}`;
beforeEach(() => {
state = getInitialState();
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
});
describe('fetchBranches', () => {
it('commits LOADING_BRANCHES and dispatch calls to fetchBranch', () =>
testAction(
actions.fetchBranches,
{ branches, projectId },
state,
[
{
type: types.LOADING_BRANCHES,
},
],
[
{
type: 'fetchBranch',
payload: {
branch: branchName,
projectId,
},
},
],
));
it('only considers unique branches', () =>
testAction(
actions.fetchBranches,
{ branches: duplicatedBranches, projectId },
state,
[
{
type: types.LOADING_BRANCHES,
},
],
[
{
type: 'fetchBranch',
payload: {
branch: branchName,
projectId,
},
},
],
));
});
describe('fetchBranch', () => {
it.each`
status | mutations
${httpStatus.OK} | ${[]}
${httpStatus.NOT_FOUND} | ${[{ type: types.INVALID_BRANCHES, payload: branchName }]}
`('triggers $mutations.length mutation when status is $status', ({ status, mutations }) => {
mock.onGet(apiEndpoint).replyOnce(status);
testAction(actions.fetchBranch, { branch: branchName, projectId }, state, mutations, []);
});
});
});
import * as types from 'ee/threat_monitoring/store/modules/scan_result_policies/mutation_types';
import mutations from 'ee/threat_monitoring/store/modules/scan_result_policies/mutations';
import getInitialState from 'ee/threat_monitoring/store/modules/scan_result_policies/state';
describe('ScanResultPolicies mutations', () => {
let state;
beforeEach(() => {
state = getInitialState();
});
describe(types.LOADING_BRANCHES, () => {
it('resets invalid branches', () => {
mutations[types.LOADING_BRANCHES](state);
expect(state.invalidBranches).toEqual([]);
});
});
describe(types.INVALID_BRANCHES, () => {
it('appends new branches into invalidBranches', () => {
const branchName = 'main';
mutations[types.INVALID_BRANCHES](state, branchName);
expect(state.invalidBranches).toEqual([branchName]);
});
});
});
...@@ -33404,6 +33404,9 @@ msgstr "" ...@@ -33404,6 +33404,9 @@ msgstr ""
msgid "SecurityConfiguration|Vulnerability details and statistics in the merge request" msgid "SecurityConfiguration|Vulnerability details and statistics in the merge request"
msgstr "" msgstr ""
msgid "SecurityOrchestration| and "
msgstr ""
msgid "SecurityOrchestration| or " msgid "SecurityOrchestration| or "
msgstr "" msgstr ""
...@@ -33632,6 +33635,9 @@ msgstr "" ...@@ -33632,6 +33635,9 @@ msgstr ""
msgid "SecurityOrchestration|Summary" msgid "SecurityOrchestration|Summary"
msgstr "" msgstr ""
msgid "SecurityOrchestration|The following branches do not exist on this development project: %{branches}. Please review all branches to ensure the values are accurate before updating this policy."
msgstr ""
msgid "SecurityOrchestration|There was a problem creating the new security policy" msgid "SecurityOrchestration|There was a problem creating the new security policy"
msgstr "" msgstr ""
......
...@@ -1733,4 +1733,36 @@ describe('Api', () => { ...@@ -1733,4 +1733,36 @@ describe('Api', () => {
}); });
}); });
}); });
describe('projectProtectedBranch', () => {
const branchName = 'new-branch-name';
const dummyProjectId = 5;
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${dummyProjectId}/protected_branches/${branchName}`;
it('returns 404 for non-existing branch', () => {
jest.spyOn(axios, 'get');
mock.onGet(expectedUrl).replyOnce(httpStatus.NOT_FOUND, {
message: '404 Not found',
});
return Api.projectProtectedBranch(dummyProjectId, branchName).catch((error) => {
expect(error.response.status).toBe(httpStatus.NOT_FOUND);
expect(axios.get).toHaveBeenCalledWith(expectedUrl);
});
});
it('returns 200 with branch information', () => {
const expectedObj = { name: branchName };
jest.spyOn(axios, 'get');
mock.onGet(expectedUrl).replyOnce(httpStatus.OK, expectedObj);
return Api.projectProtectedBranch(dummyProjectId, branchName).then((data) => {
expect(data).toEqual(expectedObj);
expect(axios.get).toHaveBeenCalledWith(expectedUrl);
});
});
});
}); });
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