From 7a10077776a729a1f7dc674c04e73b757a1dd2f4 Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Tue, 28 Jul 2020 12:53:36 +0100 Subject: [PATCH] [Security Solution] Template unit tests (#72399) * add unit test for failure cases * add unit tests * update wording * fix error when update template without ttid or ttversion * fix unit test * add comment * review Co-authored-by: Elastic Machine --- .../rules/pre_packaged_rules/translations.ts | 2 +- .../update_callout.test.tsx | 92 +++ .../pre_packaged_rules/update_callout.tsx | 9 +- .../rules/use_pre_packaged_rules.tsx | 4 +- .../detection_engine/rules/helpers.test.tsx | 136 +++++ ...get_prepackaged_rules_status_route.test.ts | 51 ++ .../routes/import_timelines_route.test.ts | 22 + .../routes/utils/compare_timelines_status.ts | 40 +- .../routes/utils/failure_cases.test.ts | 542 ++++++++++++++++++ .../timeline/routes/utils/failure_cases.ts | 17 +- 10 files changed, 888 insertions(+), 27 deletions(-) create mode 100644 x-pack/plugins/security_solution/server/lib/timeline/routes/utils/failure_cases.test.ts diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/translations.ts b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/translations.ts index 37c1715c05d71..49da7dbf6d514 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/translations.ts @@ -24,7 +24,7 @@ export const PRE_BUILT_MSG = i18n.translate( export const PRE_BUILT_ACTION = i18n.translate( 'xpack.securitySolution.detectionEngine.rules.prePackagedRules.loadPreBuiltButton', { - defaultMessage: 'Load prebuilt detection rules', + defaultMessage: 'Load prebuilt detection rules and timeline templates', } ); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/update_callout.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/update_callout.test.tsx index 5033fcd11dc7c..283bba462792c 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/update_callout.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/update_callout.test.tsx @@ -9,6 +9,7 @@ import { shallow } from 'enzyme'; import { UpdatePrePackagedRulesCallOut } from './update_callout'; import { useKibana } from '../../../../common/lib/kibana'; + jest.mock('../../../../common/lib/kibana'); describe('UpdatePrePackagedRulesCallOut', () => { @@ -22,6 +23,7 @@ describe('UpdatePrePackagedRulesCallOut', () => { }, }); }); + it('renders correctly', () => { const wrapper = shallow( { expect(wrapper.find('EuiCallOut')).toHaveLength(1); }); + + it('renders callOutMessage correctly: numberOfUpdatedRules > 0 and numberOfUpdatedTimelines = 0', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('[data-test-subj="update-callout"]').find('p').text()).toEqual( + 'You can update 1 Elastic prebuilt ruleRelease notes' + ); + }); + + it('renders buttonTitle correctly: numberOfUpdatedRules > 0 and numberOfUpdatedTimelines = 0', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('[data-test-subj="update-callout-button"]').prop('children')).toEqual( + 'Update 1 Elastic prebuilt rule' + ); + }); + + it('renders callOutMessage correctly: numberOfUpdatedRules = 0 and numberOfUpdatedTimelines > 0', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('[data-test-subj="update-callout"]').find('p').text()).toEqual( + 'You can update 1 Elastic prebuilt timelineRelease notes' + ); + }); + + it('renders buttonTitle correctly: numberOfUpdatedRules = 0 and numberOfUpdatedTimelines > 0', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('[data-test-subj="update-callout-button"]').prop('children')).toEqual( + 'Update 1 Elastic prebuilt timeline' + ); + }); + + it('renders callOutMessage correctly: numberOfUpdatedRules > 0 and numberOfUpdatedTimelines > 0', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('[data-test-subj="update-callout"]').find('p').text()).toEqual( + 'You can update 1 Elastic prebuilt rule and 1 Elastic prebuilt timeline. Note that this will reload deleted Elastic prebuilt rules.Release notes' + ); + }); + + it('renders buttonTitle correctly: numberOfUpdatedRules > 0 and numberOfUpdatedTimelines > 0', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('[data-test-subj="update-callout-button"]').prop('children')).toEqual( + 'Update 1 Elastic prebuilt rule and 1 Elastic prebuilt timeline' + ); + }); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/update_callout.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/update_callout.tsx index 4b454a9ed4d4a..30f8cfa7fb3a5 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/update_callout.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/update_callout.tsx @@ -51,7 +51,7 @@ const UpdatePrePackagedRulesCallOutComponent: React.FC +

{prepackagedRulesOrTimelines?.callOutMessage}
@@ -62,7 +62,12 @@ const UpdatePrePackagedRulesCallOutComponent: React.FC

- + {prepackagedRulesOrTimelines?.buttonTitle}
diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.tsx index 08c85695e9313..d82d97883a3d0 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.tsx @@ -169,7 +169,9 @@ export const usePrePackagedRules = ({ if ( isSubscribed && ((prePackagedRuleStatusResponse.rules_not_installed === 0 && - prePackagedRuleStatusResponse.rules_not_updated === 0) || + prePackagedRuleStatusResponse.rules_not_updated === 0 && + prePackagedRuleStatusResponse.timelines_not_installed === 0 && + prePackagedRuleStatusResponse.timelines_not_updated === 0) || iterationTryOfFetchingPrePackagedCount > 100) ) { setLoadingCreatePrePackagedRules(false); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx index c01317e4f48c5..b40243efcfb46 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx @@ -13,6 +13,8 @@ import { getActionsStepsData, getHumanizedDuration, getModifiedAboutDetailsData, + getPrePackagedRuleStatus, + getPrePackagedTimelineStatus, determineDetailsValue, userHasNoPermissions, } from './helpers'; @@ -394,4 +396,138 @@ describe('rule helpers', () => { expect(result).toEqual(userHasNoPermissionsExpectedResult); }); }); + + describe('getPrePackagedRuleStatus', () => { + test('ruleNotInstalled', () => { + const rulesInstalled = 0; + const rulesNotInstalled = 1; + const rulesNotUpdated = 0; + const result: string = getPrePackagedRuleStatus( + rulesInstalled, + rulesNotInstalled, + rulesNotUpdated + ); + + expect(result).toEqual('ruleNotInstalled'); + }); + + test('ruleInstalled', () => { + const rulesInstalled = 1; + const rulesNotInstalled = 0; + const rulesNotUpdated = 0; + const result: string = getPrePackagedRuleStatus( + rulesInstalled, + rulesNotInstalled, + rulesNotUpdated + ); + + expect(result).toEqual('ruleInstalled'); + }); + + test('someRuleUninstall', () => { + const rulesInstalled = 1; + const rulesNotInstalled = 1; + const rulesNotUpdated = 0; + const result: string = getPrePackagedRuleStatus( + rulesInstalled, + rulesNotInstalled, + rulesNotUpdated + ); + + expect(result).toEqual('someRuleUninstall'); + }); + + test('ruleNeedUpdate', () => { + const rulesInstalled = 1; + const rulesNotInstalled = 0; + const rulesNotUpdated = 1; + const result: string = getPrePackagedRuleStatus( + rulesInstalled, + rulesNotInstalled, + rulesNotUpdated + ); + + expect(result).toEqual('ruleNeedUpdate'); + }); + + test('unknown', () => { + const rulesInstalled = null; + const rulesNotInstalled = null; + const rulesNotUpdated = null; + const result: string = getPrePackagedRuleStatus( + rulesInstalled, + rulesNotInstalled, + rulesNotUpdated + ); + + expect(result).toEqual('unknown'); + }); + }); + + describe('getPrePackagedTimelineStatus', () => { + test('timelinesNotInstalled', () => { + const timelinesInstalled = 0; + const timelinesNotInstalled = 1; + const timelinesNotUpdated = 0; + const result: string = getPrePackagedTimelineStatus( + timelinesInstalled, + timelinesNotInstalled, + timelinesNotUpdated + ); + + expect(result).toEqual('timelinesNotInstalled'); + }); + + test('timelinesInstalled', () => { + const timelinesInstalled = 1; + const timelinesNotInstalled = 0; + const timelinesNotUpdated = 0; + const result: string = getPrePackagedTimelineStatus( + timelinesInstalled, + timelinesNotInstalled, + timelinesNotUpdated + ); + + expect(result).toEqual('timelinesInstalled'); + }); + + test('someTimelineUninstall', () => { + const timelinesInstalled = 1; + const timelinesNotInstalled = 1; + const timelinesNotUpdated = 0; + const result: string = getPrePackagedTimelineStatus( + timelinesInstalled, + timelinesNotInstalled, + timelinesNotUpdated + ); + + expect(result).toEqual('someTimelineUninstall'); + }); + + test('timelineNeedUpdate', () => { + const timelinesInstalled = 1; + const timelinesNotInstalled = 0; + const timelinesNotUpdated = 1; + const result: string = getPrePackagedTimelineStatus( + timelinesInstalled, + timelinesNotInstalled, + timelinesNotUpdated + ); + + expect(result).toEqual('timelineNeedUpdate'); + }); + + test('unknown', () => { + const timelinesInstalled = null; + const timelinesNotInstalled = null; + const timelinesNotUpdated = null; + const result: string = getPrePackagedTimelineStatus( + timelinesInstalled, + timelinesNotInstalled, + timelinesNotUpdated + ); + + expect(result).toEqual('unknown'); + }); + }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.test.ts index f8b6f7e3ddcba..fa2a575d3f69f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.test.ts @@ -14,6 +14,11 @@ import { } from '../__mocks__/request_responses'; import { requestContextMock, serverMock, createMockConfig } from '../__mocks__'; import { SecurityPluginSetup } from '../../../../../../security/server'; +import { checkTimelinesStatus } from '../../../timeline/routes/utils/check_timelines_status'; +import { + mockCheckTimelinesStatusBeforeInstallResult, + mockCheckTimelinesStatusAfterInstallResult, +} from '../../../timeline/routes/__mocks__/import_timelines'; jest.mock('../../rules/get_prepackaged_rules', () => { return { @@ -38,6 +43,12 @@ jest.mock('../../rules/get_prepackaged_rules', () => { }; }); +jest.mock('../../../timeline/routes/utils/check_timelines_status', () => { + return { + checkTimelinesStatus: jest.fn(), + }; +}); + describe('get_prepackaged_rule_status_route', () => { const mockGetCurrentUser = { user: { @@ -126,5 +137,45 @@ describe('get_prepackaged_rule_status_route', () => { timelines_not_updated: 0, }); }); + + test('0 timelines installed, 3 timelines not installed, 0 timelines not updated', async () => { + clients.alertsClient.find.mockResolvedValue(getEmptyFindResult()); + (checkTimelinesStatus as jest.Mock).mockResolvedValue( + mockCheckTimelinesStatusBeforeInstallResult + ); + const request = getPrepackagedRulesStatusRequest(); + const response = await server.inject(request, context); + + expect(response.status).toEqual(200); + expect(response.body).toEqual({ + rules_custom_installed: 0, + rules_installed: 0, + rules_not_installed: 1, + rules_not_updated: 0, + timelines_installed: 0, + timelines_not_installed: 3, + timelines_not_updated: 0, + }); + }); + + test('3 timelines installed, 0 timelines not installed, 0 timelines not updated', async () => { + clients.alertsClient.find.mockResolvedValue(getEmptyFindResult()); + (checkTimelinesStatus as jest.Mock).mockResolvedValue( + mockCheckTimelinesStatusAfterInstallResult + ); + const request = getPrepackagedRulesStatusRequest(); + const response = await server.inject(request, context); + + expect(response.status).toEqual(200); + expect(response.body).toEqual({ + rules_custom_installed: 0, + rules_installed: 0, + rules_not_installed: 1, + rules_not_updated: 0, + timelines_installed: 3, + timelines_not_installed: 0, + timelines_not_updated: 0, + }); + }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/import_timelines_route.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/import_timelines_route.test.ts index fe5993cb0161d..b817896e901c1 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/import_timelines_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/import_timelines_route.test.ts @@ -598,6 +598,28 @@ describe('import timeline templates', () => { mockNewTemplateTimelineId ); }); + + test('should return 200 if create via import without a templateTimelineId or templateTimelineVersion', async () => { + mockGetTupleDuplicateErrorsAndUniqueTimeline.mockReturnValue([ + mockDuplicateIdErrors, + [ + { + ...mockUniqueParsedTemplateTimelineObjects[0], + templateTimelineId: null, + templateTimelineVersion: null, + }, + ], + ]); + const mockRequest = getImportTimelinesRequest(); + const result = await server.inject(mockRequest, context); + expect(result.body).toEqual({ + errors: [], + success: true, + success_count: 1, + timelines_installed: 1, + timelines_updated: 0, + }); + }); }); describe('Import a timeline template already exist', () => { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/compare_timelines_status.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/compare_timelines_status.ts index d61d217a4cf49..f9515741d1250 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/compare_timelines_status.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/compare_timelines_status.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { isEmpty } from 'lodash/fp'; +import { isEmpty, isInteger } from 'lodash/fp'; import { TimelineTypeLiteralWithNull, TimelineType, @@ -71,13 +71,28 @@ export class CompareTimelinesStatus { } public get isCreatable() { + const noExistingTimeline = this.timelineObject.isCreatable && !this.isHandlingTemplateTimeline; + + const templateCreatable = + this.isHandlingTemplateTimeline && this.templateTimelineObject.isCreatable; + + const noExistingTimelineOrTemplate = templateCreatable && this.timelineObject.isCreatable; + + // From Line 87-91 is the condition for creating a template via import without given a templateTimelineId or templateTimelineVersion, + // but keep the existing savedObjectId and version there. + // Therefore even the timeline exists, we still allow it to create a new timeline template by assigning a templateTimelineId and templateTimelineVersion. + // https://github.com/elastic/kibana/pull/67496#discussion_r454337222 + // Line 90-91 means that we want to make sure the existing timeline retrieved by savedObjectId is atemplate. + // If it is not a template, we show an error this timeline is already exist instead. + const retriveTemplateViaSavedObjectId = + templateCreatable && + !this.timelineObject.isCreatable && + this.timelineObject.getData?.timelineType === this.timelineType; + return ( this.isTitleValid && !this.isSavedObjectVersionConflict && - ((this.timelineObject.isCreatable && !this.isHandlingTemplateTimeline) || - (this.templateTimelineObject.isCreatable && - this.timelineObject.isCreatable && - this.isHandlingTemplateTimeline)) + (noExistingTimeline || noExistingTimelineOrTemplate || retriveTemplateViaSavedObjectId) ); } @@ -195,24 +210,27 @@ export class CompareTimelinesStatus { } private get isTemplateVersionConflict() { - const version = this.templateTimelineObject?.getVersion; + const templateTimelineVersion = this.templateTimelineObject?.getVersion; const existingTemplateTimelineVersion = this.templateTimelineObject?.data ?.templateTimelineVersion; if ( - version != null && + templateTimelineVersion != null && this.templateTimelineObject.isExists && existingTemplateTimelineVersion != null ) { - return version <= existingTemplateTimelineVersion; - } else if (this.templateTimelineObject.isExists && version == null) { + return templateTimelineVersion <= existingTemplateTimelineVersion; + } else if (this.templateTimelineObject.isExists && templateTimelineVersion == null) { return true; } return false; } private get isTemplateVersionValid() { - const version = this.templateTimelineObject?.getVersion; - return typeof version === 'number' && !this.isTemplateVersionConflict; + const templateTimelineVersion = this.templateTimelineObject?.getVersion; + return ( + templateTimelineVersion == null || + (isInteger(templateTimelineVersion) && !this.isTemplateVersionConflict) + ); } private get isUpdatedTimelineStatusValid() { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/failure_cases.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/failure_cases.test.ts new file mode 100644 index 0000000000000..3c3ad1cf2d7f8 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/failure_cases.test.ts @@ -0,0 +1,542 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + commonFailureChecker, + checkIsCreateFailureCases, + checkIsUpdateFailureCases, + checkIsCreateViaImportFailureCases, + EMPTY_TITLE_ERROR_MESSAGE, + UPDATE_STATUS_ERROR_MESSAGE, + CREATE_TIMELINE_ERROR_MESSAGE, + CREATE_TEMPLATE_TIMELINE_ERROR_MESSAGE, + CREATE_TEMPLATE_TIMELINE_WITHOUT_VERSION_ERROR_MESSAGE, + NO_MATCH_ID_ERROR_MESSAGE, + NO_MATCH_VERSION_ERROR_MESSAGE, + NOT_ALLOW_UPDATE_TIMELINE_TYPE_ERROR_MESSAGE, + UPDATE_TEMPLATE_TIMELINE_ERROR_MESSAGE, + CREATE_WITH_INVALID_STATUS_ERROR_MESSAGE, + getImportExistingTimelineError, + checkIsUpdateViaImportFailureCases, + NOT_ALLOW_UPDATE_STATUS_ERROR_MESSAGE, + TEMPLATE_TIMELINE_VERSION_CONFLICT_MESSAGE, +} from './failure_cases'; +import { + TimelineStatus, + TimelineType, + TimelineSavedObject, +} from '../../../../../common/types/timeline'; +import { mockGetTimelineValue, mockGetTemplateTimelineValue } from '../__mocks__/import_timelines'; + +describe('failure cases', () => { + describe('commonFailureChecker', () => { + test('If timeline type is draft, it should not return error if title is not given', () => { + const result = commonFailureChecker(TimelineStatus.draft, null); + + expect(result).toBeNull(); + }); + + test('If timeline type is active, it should return error if title is not given', () => { + const result = commonFailureChecker(TimelineStatus.active, null); + + expect(result).toEqual({ + body: EMPTY_TITLE_ERROR_MESSAGE, + statusCode: 405, + }); + }); + + test('If timeline type is immutable, it should return error if title is not given', () => { + const result = commonFailureChecker(TimelineStatus.immutable, null); + + expect(result).toEqual({ + body: EMPTY_TITLE_ERROR_MESSAGE, + statusCode: 405, + }); + }); + + test('If timeline type is not a draft, it should return no error if title is given', () => { + const result = commonFailureChecker(TimelineStatus.active, 'title'); + + expect(result).toBeNull(); + }); + }); + + describe('checkIsCreateFailureCases', () => { + test('Should return error if trying to create a timeline that is already exist', () => { + const isHandlingTemplateTimeline = false; + const version = null; + const templateTimelineVersion = null; + const templateTimelineId = null; + const existTimeline = mockGetTimelineValue as TimelineSavedObject; + const existTemplateTimeline = null; + const result = checkIsCreateFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.default, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: CREATE_TIMELINE_ERROR_MESSAGE, + statusCode: 405, + }); + }); + + test('Should return error if trying to create a timeline template that is already exist', () => { + const isHandlingTemplateTimeline = true; + const version = null; + const templateTimelineVersion = 1; + const templateTimelineId = 'template-timeline-id-one'; + const existTimeline = null; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const result = checkIsCreateFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.template, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: CREATE_TEMPLATE_TIMELINE_ERROR_MESSAGE, + statusCode: 405, + }); + }); + + test('Should return error if trying to create a timeline template without providing templateTimelineVersion', () => { + const isHandlingTemplateTimeline = true; + const version = null; + const templateTimelineVersion = null; + const templateTimelineId = 'template-timeline-id-one'; + const existTimeline = null; + const existTemplateTimeline = null; + const result = checkIsCreateFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.template, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: CREATE_TEMPLATE_TIMELINE_WITHOUT_VERSION_ERROR_MESSAGE, + statusCode: 403, + }); + }); + }); + + describe('checkIsUpdateFailureCases', () => { + test('Should return error if trying to update status field of an existing immutable timeline', () => { + const isHandlingTemplateTimeline = false; + const version = mockGetTimelineValue.version; + const templateTimelineVersion = null; + const templateTimelineId = null; + const existTimeline = { + ...(mockGetTimelineValue as TimelineSavedObject), + status: TimelineStatus.immutable, + }; + const existTemplateTimeline = null; + const result = checkIsUpdateFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.default, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: UPDATE_STATUS_ERROR_MESSAGE, + statusCode: 403, + }); + }); + + test('Should return error if trying to update status field of an existing immutable timeline template', () => { + const isHandlingTemplateTimeline = true; + const version = mockGetTemplateTimelineValue.version; + const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; + const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; + const existTimeline = null; + const existTemplateTimeline = { + ...(mockGetTemplateTimelineValue as TimelineSavedObject), + status: TimelineStatus.immutable, + }; + const result = checkIsUpdateFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.template, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: UPDATE_STATUS_ERROR_MESSAGE, + statusCode: 403, + }); + }); + + test('should return error if trying to update timelineType field of an existing timeline template', () => { + const isHandlingTemplateTimeline = true; + const version = mockGetTemplateTimelineValue.version; + const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; + const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; + const existTimeline = null; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const result = checkIsUpdateFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.default, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: NOT_ALLOW_UPDATE_TIMELINE_TYPE_ERROR_MESSAGE, + statusCode: 403, + }); + }); + + test('should return error if trying to update a timeline template that does not exist', () => { + const isHandlingTemplateTimeline = true; + const version = mockGetTemplateTimelineValue.version; + const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; + const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; + const existTimeline = null; + const existTemplateTimeline = null; + const result = checkIsUpdateFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.default, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: UPDATE_TEMPLATE_TIMELINE_ERROR_MESSAGE, + statusCode: 405, + }); + }); + + test('should return error if there is no matched timeline found by given templateTimelineId', () => { + const isHandlingTemplateTimeline = true; + const version = mockGetTemplateTimelineValue.version; + const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; + const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; + const existTimeline = { + ...(mockGetTemplateTimelineValue as TimelineSavedObject), + savedObjectId: 'someOtherId', + }; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const result = checkIsUpdateFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.template, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: NO_MATCH_ID_ERROR_MESSAGE, + statusCode: 409, + }); + }); + + test('should return error if given version field is defferent from existing version of timelin template', () => { + const isHandlingTemplateTimeline = true; + const version = 'xxx'; + const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; + const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; + const existTimeline = null; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const result = checkIsUpdateFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.template, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: NO_MATCH_VERSION_ERROR_MESSAGE, + statusCode: 409, + }); + }); + }); + + describe('checkIsCreateViaImportFailureCases', () => { + test('should return error if trying to create a draft timeline', () => { + const isHandlingTemplateTimeline = true; + const version = mockGetTemplateTimelineValue.version; + const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; + const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; + const existTimeline = null; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const result = checkIsCreateViaImportFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.draft, + TimelineType.template, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: CREATE_WITH_INVALID_STATUS_ERROR_MESSAGE, + statusCode: 405, + }); + }); + + test('should return error if trying to create a timeline template which is already exist', () => { + const isHandlingTemplateTimeline = true; + const version = mockGetTemplateTimelineValue.version; + const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; + const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; + const existTimeline = null; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const result = checkIsCreateViaImportFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.template, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: getImportExistingTimelineError(mockGetTimelineValue.savedObjectId), + statusCode: 405, + }); + }); + + test('should return error if importe a timeline which is already exists', () => { + const isHandlingTemplateTimeline = false; + const version = mockGetTimelineValue.version; + const templateTimelineVersion = null; + const templateTimelineId = null; + const existTimeline = mockGetTimelineValue as TimelineSavedObject; + const existTemplateTimeline = null; + const result = checkIsCreateViaImportFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.default, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: getImportExistingTimelineError(mockGetTimelineValue.savedObjectId), + statusCode: 405, + }); + }); + }); + + describe('checkIsUpdateViaImportFailureCases', () => { + test('should return error if trying to update a timeline which does not exist', () => { + const isHandlingTemplateTimeline = false; + const version = mockGetTimelineValue.version; + const templateTimelineVersion = null; + const templateTimelineId = null; + const existTimeline = mockGetTimelineValue as TimelineSavedObject; + const existTemplateTimeline = null; + const result = checkIsUpdateViaImportFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.default, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: getImportExistingTimelineError(mockGetTimelineValue.savedObjectId), + statusCode: 405, + }); + }); + + test('should return error if trying to update timelineType field of an existing timeline template', () => { + const isHandlingTemplateTimeline = true; + const version = mockGetTemplateTimelineValue.version; + const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; + const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; + const existTimeline = null; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const result = checkIsUpdateViaImportFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.default, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: NOT_ALLOW_UPDATE_TIMELINE_TYPE_ERROR_MESSAGE, + statusCode: 403, + }); + }); + + test('should return error if trying to update status field of an existing timeline template', () => { + const isHandlingTemplateTimeline = true; + const version = mockGetTemplateTimelineValue.version; + const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; + const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; + const existTimeline = null; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const result = checkIsUpdateViaImportFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.immutable, + TimelineType.template, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: NOT_ALLOW_UPDATE_STATUS_ERROR_MESSAGE, + statusCode: 405, + }); + }); + + test('should return error if trying to update a timeline template that does not exist', () => { + const isHandlingTemplateTimeline = true; + const version = mockGetTemplateTimelineValue.version; + const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; + const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; + const existTimeline = null; + const existTemplateTimeline = null; + const result = checkIsUpdateViaImportFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.default, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: UPDATE_TEMPLATE_TIMELINE_ERROR_MESSAGE, + statusCode: 405, + }); + }); + + test('should return error if there is no matched timeline found by given templateTimelineId', () => { + const isHandlingTemplateTimeline = true; + const version = mockGetTemplateTimelineValue.version; + const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; + const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; + const existTimeline = { + ...(mockGetTemplateTimelineValue as TimelineSavedObject), + savedObjectId: 'someOtherId', + }; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const result = checkIsUpdateViaImportFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.template, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: NO_MATCH_ID_ERROR_MESSAGE, + statusCode: 409, + }); + }); + + test('should return error if given version field is defferent from existing version of timelin template', () => { + const isHandlingTemplateTimeline = true; + const version = 'xxx'; + const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; + const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; + const existTimeline = null; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const result = checkIsUpdateViaImportFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.template, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: NO_MATCH_VERSION_ERROR_MESSAGE, + statusCode: 409, + }); + }); + + test('should return error if given templateTimelineVersion field is less or equal to existing templateTimelineVersion of timelin template', () => { + const isHandlingTemplateTimeline = true; + const version = mockGetTemplateTimelineValue.version; + const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; + const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; + const existTimeline = null; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const result = checkIsUpdateViaImportFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.template, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: TEMPLATE_TIMELINE_VERSION_CONFLICT_MESSAGE, + statusCode: 409, + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/failure_cases.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/failure_cases.ts index d41e8fc190983..b926819d66c92 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/failure_cases.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/failure_cases.ts @@ -78,7 +78,10 @@ const commonUpdateTemplateTimelineCheck = ( existTemplateTimeline: TimelineSavedObject | null ) => { if (isHandlingTemplateTimeline) { - if (existTimeline != null && timelineType !== existTimeline.timelineType) { + if ( + (existTimeline != null && timelineType !== existTimeline.timelineType) || + (existTemplateTimeline != null && timelineType !== existTemplateTimeline.timelineType) + ) { return { body: NOT_ALLOW_UPDATE_TIMELINE_TYPE_ERROR_MESSAGE, statusCode: 403, @@ -106,11 +109,7 @@ const commonUpdateTemplateTimelineCheck = ( }; } - if ( - existTemplateTimeline != null && - existTemplateTimeline.templateTimelineVersion == null && - existTemplateTimeline.version !== version - ) { + if (existTemplateTimeline != null && existTemplateTimeline.version !== version) { // throw error 409 conflict timeline return { body: NO_MATCH_VERSION_ERROR_MESSAGE, @@ -231,12 +230,6 @@ export const checkIsUpdateViaImportFailureCases = ( }; } } else { - if (existTemplateTimeline != null && timelineType !== existTemplateTimeline?.timelineType) { - return { - body: NOT_ALLOW_UPDATE_TIMELINE_TYPE_ERROR_MESSAGE, - statusCode: 403, - }; - } const isStatusValid = ((existTemplateTimeline?.status == null || existTemplateTimeline?.status === TimelineStatus.active) &&