diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines_export.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines_export.spec.ts new file mode 100644 index 0000000000000..d8f96aaf5e563 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/integration/timelines_export.spec.ts @@ -0,0 +1,41 @@ +/* + * 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 { exportTimeline, waitForTimelinesPanelToBeLoaded } from '../tasks/timeline'; +import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; +import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; + +import { TIMELINES_URL } from '../urls/navigation'; + +const EXPECTED_EXPORTED_TIMELINE_PATH = 'cypress/test_files/expected_timelines_export.ndjson'; + +describe('Export timelines', () => { + before(() => { + esArchiverLoad('timeline'); + cy.server(); + cy.route('POST', '**api/timeline/_export?file_name=timelines_export.ndjson*').as('export'); + }); + + after(() => { + esArchiverUnload('timeline'); + }); + + it('Exports a custom timeline', () => { + loginAndWaitForPageWithoutDateRange(TIMELINES_URL); + waitForTimelinesPanelToBeLoaded(); + + cy.readFile(EXPECTED_EXPORTED_TIMELINE_PATH).then(($expectedExportedJson) => { + const parsedJson = JSON.parse($expectedExportedJson); + const timelineId = parsedJson.savedObjectId; + exportTimeline(timelineId); + + cy.wait('@export').then((response) => { + cy.wrap(response.status).should('eql', 200); + cy.wrap(response.xhr.responseText).should('eql', $expectedExportedJson); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/screens/timeline.ts b/x-pack/plugins/security_solution/cypress/screens/timeline.ts index 26203a8ca3b83..fd41cd63fc090 100644 --- a/x-pack/plugins/security_solution/cypress/screens/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/screens/timeline.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +export const BULK_ACTIONS = '[data-test-subj="utility-bar-action-button"]'; + export const CLOSE_TIMELINE_BTN = '[data-test-subj="close-timeline"]'; export const CREATE_NEW_TIMELINE = '[data-test-subj="timeline-new"]'; @@ -11,6 +13,8 @@ export const CREATE_NEW_TIMELINE = '[data-test-subj="timeline-new"]'; export const DRAGGABLE_HEADER = '[data-test-subj="events-viewer-panel"] [data-test-subj="headers-group"] [data-test-subj="draggable-header"]'; +export const EXPORT_TIMELINE_ACTION = '[data-test-subj="export-timeline-action"]'; + export const HEADER = '[data-test-subj="header"]'; export const HEADERS_GROUP = '[data-test-subj="headers-group"]'; @@ -41,6 +45,10 @@ export const TIMELINE = (id: string) => { export const TIMELINE_CHANGES_IN_PROGRESS = '[data-test-subj="timeline"] .euiProgress'; +export const TIMELINE_CHECKBOX = (id: string) => { + return `[data-test-subj="checkboxSelectRow-${id}"]`; +}; + export const TIMELINE_COLUMN_SPINNER = '[data-test-subj="timeline-loading-spinner"]'; export const TIMELINE_DATA_PROVIDERS = '[data-test-subj="dataProviders"]'; @@ -70,6 +78,8 @@ export const TIMELINE_SETTINGS_ICON = '[data-test-subj="settings-gear"]'; export const TIMELINE_TITLE = '[data-test-subj="timeline-title"]'; +export const TIMELINES_TABLE = '[data-test-subj="timelines-table"]'; + export const TIMESTAMP_HEADER_FIELD = '[data-test-subj="header-text-@timestamp"]'; export const TIMESTAMP_TOGGLE_FIELD = '[data-test-subj="toggle-field-@timestamp"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts index 08624df06e096..6fb8bb5e29ae5 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts @@ -5,8 +5,11 @@ */ import { + BULK_ACTIONS, CLOSE_TIMELINE_BTN, CREATE_NEW_TIMELINE, + EXPORT_TIMELINE_ACTION, + TIMELINE_CHECKBOX, HEADER, ID_FIELD, ID_HEADER_FIELD, @@ -20,6 +23,7 @@ import { TIMELINE_INSPECT_BUTTON, TIMELINE_SETTINGS_ICON, TIMELINE_TITLE, + TIMELINES_TABLE, TIMESTAMP_TOGGLE_FIELD, TOGGLE_TIMELINE_EXPAND_EVENT, REMOVE_COLUMN, @@ -66,6 +70,12 @@ export const expandFirstTimelineEventDetails = () => { cy.get(TOGGLE_TIMELINE_EXPAND_EVENT).first().click({ force: true }); }; +export const exportTimeline = (timelineId: string) => { + cy.get(TIMELINE_CHECKBOX(timelineId)).click({ force: true }); + cy.get(BULK_ACTIONS).click({ force: true }); + cy.get(EXPORT_TIMELINE_ACTION).click(); +}; + export const openTimelineFieldsBrowser = () => { cy.get(TIMELINE_FIELDS_BUTTON).click({ force: true }); }; @@ -122,6 +132,10 @@ export const resetFields = () => { cy.get(RESET_FIELDS).click({ force: true }); }; +export const waitForTimelinesPanelToBeLoaded = () => { + cy.get(TIMELINES_TABLE).should('exist'); +}; + export const waitForTimelineChanges = () => { cy.get(TIMELINE_CHANGES_IN_PROGRESS).should('exist'); cy.get(TIMELINE_CHANGES_IN_PROGRESS).should('not.exist'); diff --git a/x-pack/plugins/security_solution/cypress/test_files/expected_timelines_export.ndjson b/x-pack/plugins/security_solution/cypress/test_files/expected_timelines_export.ndjson new file mode 100644 index 0000000000000..9cca356a8b052 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/test_files/expected_timelines_export.ndjson @@ -0,0 +1 @@ +{"savedObjectId":"0162c130-78be-11ea-9718-118a926974a4","version":"WzcsMV0=","columns":[{"columnHeaderType":"not-filtered","id":"@timestamp"},{"columnHeaderType":"not-filtered","id":"message"},{"columnHeaderType":"not-filtered","id":"event.category"},{"columnHeaderType":"not-filtered","id":"event.action"},{"columnHeaderType":"not-filtered","id":"host.name"},{"columnHeaderType":"not-filtered","id":"source.ip"},{"columnHeaderType":"not-filtered","id":"destination.ip"},{"columnHeaderType":"not-filtered","id":"user.name"}],"created":1586256805054,"createdBy":"elastic","dataProviders":[],"dateRange":{"end":1586256837669,"start":1546343624710},"description":"description","eventType":"all","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"expression":"host.name:*","kind":"kuery"},"serializedQuery":"{\"bool\":{\"should\":[{\"exists\":{\"field\":\"host.name\"}}],\"minimum_should_match\":1}}"}},"savedQueryId":null,"sort":{"columnId":"@timestamp","sortDirection":"desc"},"title":"SIEM test","updated":1586256839298,"updatedBy":"elastic","timelineType":"default","eventNotes":[],"globalNotes":[],"pinnedEventIds":[]} diff --git a/x-pack/plugins/security_solution/public/common/components/link_icon/index.tsx b/x-pack/plugins/security_solution/public/common/components/link_icon/index.tsx index 75d396fe384f8..19f1d70e6e230 100644 --- a/x-pack/plugins/security_solution/public/common/components/link_icon/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/link_icon/index.tsx @@ -6,7 +6,7 @@ import { EuiIcon, EuiLink, IconSize, IconType } from '@elastic/eui'; import { LinkAnchorProps } from '@elastic/eui/src/components/link/link'; -import React from 'react'; +import React, { ReactNode } from 'react'; import styled, { css } from 'styled-components'; interface LinkProps { @@ -47,7 +47,7 @@ export const Link = styled(({ iconSide, children, ...rest }) => ( Link.displayName = 'Link'; export interface LinkIconProps extends LinkProps { - children: string; + children: string | ReactNode; iconSize?: IconSize; iconType: IconType; dataTestSubj?: string; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx index 1f5f0ccca3b70..e9ae66703f017 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx @@ -224,7 +224,7 @@ export const OpenTimeline = React.memo( popoverContent={getBatchItemsPopoverContent} data-test-subj="utility-bar-action" > - {i18n.BATCH_ACTIONS} + {i18n.BATCH_ACTIONS} )} diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/add_prepackaged_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/add_prepackaged_rules.ts index a022b7c79c079..c682c1f1f4640 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/add_prepackaged_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/add_prepackaged_rules.ts @@ -49,7 +49,7 @@ export default ({ getService }: FtrProviderContext): void => { await deleteAllTimelines(es); }); - it('should contain two output keys of rules_installed and rules_updated', async () => { + it('should contain rules_installed, rules_updated, timelines_installed, and timelines_updated', async () => { const { body } = await supertest .put(DETECTION_ENGINE_PREPACKAGED_URL) .set('kbn-xsrf', 'true') @@ -74,6 +74,16 @@ export default ({ getService }: FtrProviderContext): void => { expect(body.rules_installed).to.be.greaterThan(0); }); + it('should create the prepackaged timelines and return a count greater than zero', async () => { + const { body } = await supertest + .put(DETECTION_ENGINE_PREPACKAGED_URL) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(body.timelines_installed).to.be.greaterThan(0); + }); + it('should create the prepackaged rules that the rules_updated is of size zero', async () => { const { body } = await supertest .put(DETECTION_ENGINE_PREPACKAGED_URL) @@ -84,6 +94,16 @@ export default ({ getService }: FtrProviderContext): void => { expect(body.rules_updated).to.eql(0); }); + it('should create the prepackaged timelines and the timelines_updated is of size zero', async () => { + const { body } = await supertest + .put(DETECTION_ENGINE_PREPACKAGED_URL) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(body.timelines_updated).to.eql(0); + }); + it('should be possible to call the API twice and the second time the number of rules installed should be zero', async () => { await supertest .put(DETECTION_ENGINE_PREPACKAGED_URL) @@ -109,6 +129,30 @@ export default ({ getService }: FtrProviderContext): void => { expect(body.rules_installed).to.eql(0); }); + + it('should be possible to call the API twice and the second time the number of timelines installed should be zero', async () => { + await supertest + .put(DETECTION_ENGINE_PREPACKAGED_URL) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + await waitFor(async () => { + const { body } = await supertest + .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) + .set('kbn-xsrf', 'true') + .expect(200); + return body.timelines_not_installed === 0; + }); + + const { body } = await supertest + .put(DETECTION_ENGINE_PREPACKAGED_URL) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(body.timelines_installed).to.eql(0); + }); }); }); }; diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/import_timelines.ts b/x-pack/test/detection_engine_api_integration/basic/tests/import_timelines.ts new file mode 100644 index 0000000000000..209c7974a7c2a --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/basic/tests/import_timelines.ts @@ -0,0 +1,403 @@ +/* + * 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 expect from '@kbn/expect'; + +import { TIMELINE_IMPORT_URL } from '../../../../plugins/security_solution/common/constants'; + +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { deleteAllTimelines } from '../../utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + const es = getService('es'); + + describe('import timelines', () => { + describe('creating a timeline', () => { + const getTimeline = () => { + return Buffer.from( + JSON.stringify({ + savedObjectId: '67664480-d191-11ea-ae67-4f4be8c1847b', + version: 'WzU1NSwxXQ==', + columns: [ + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: '@timestamp', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'message', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'event.category', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'event.action', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'host.name', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'source.ip', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'destination.ip', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'user.name', + searchable: null, + }, + ], + dataProviders: [], + description: '', + eventType: 'all', + filters: [], + kqlMode: 'filter', + timelineType: 'default', + kqlQuery: { + filterQuery: { + serializedQuery: + '{"bool":{"should":[{"exists":{"field":"@timestamp"}}],"minimum_should_match":1}}', + kuery: { + expression: '@timestamp : * ', + kind: 'kuery', + }, + }, + }, + title: 'x2', + sort: { + columnId: '@timestamp', + sortDirection: 'desc', + }, + created: 1596036895488, + createdBy: 'angela', + updated: 1596491470411, + updatedBy: 'elastic', + templateTimelineId: null, + templateTimelineVersion: null, + dateRange: { + start: '2020-04-10T14:10:58.373Z', + end: '2020-05-30T14:16:58.373Z', + }, + savedQueryId: null, + eventNotes: [ + { + noteId: '7d875ba0-d5d3-11ea-9899-ebec3d084fe0', + version: 'WzU1NiwxXQ==', + eventId: '8KtMKnIBOS_moQ_K9fAe', + note: 'hi Xavier', + timelineId: '67664480-d191-11ea-ae67-4f4be8c1847b', + created: 1596491490806, + createdBy: 'elastic', + updated: 1596491490806, + updatedBy: 'elastic', + }, + ], + globalNotes: [], + pinnedEventIds: [ + 'K99zy3EBDTDlbwBfpf6x', + 'GKpFKnIBOS_moQ_Ke5AO', + '8KtMKnIBOS_moQ_K9fAe', + ], + }) + ); + }; + beforeEach(async () => {}); + + afterEach(async () => { + await deleteAllTimelines(es); + }); + + it("if it doesn't exists", async () => { + const { body } = await supertest + .post(`${TIMELINE_IMPORT_URL}`) + .set('kbn-xsrf', 'true') + .attach('file', getTimeline(), 'timelines.ndjson') + .expect(200); + expect(body).to.eql({ + errors: [], + success: true, + success_count: 1, + timelines_installed: 1, + timelines_updated: 0, + }); + }); + }); + + describe('creating a timeline template', () => { + const getTimelineTemplate = () => { + return Buffer.from( + JSON.stringify({ + savedObjectId: 'cab434d0-d26c-11ea-b887-3b103296472a', + version: 'WzQ0NSwxXQ==', + columns: [ + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: '@timestamp', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'message', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'event.category', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'event.action', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'host.name', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'source.ip', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'destination.ip', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'user.name', + searchable: null, + }, + ], + dataProviders: [], + description: 'desc', + eventType: 'all', + filters: [], + kqlMode: 'filter', + timelineType: 'template', + kqlQuery: { filterQuery: null }, + title: 'my t template', + sort: { columnId: '@timestamp', sortDirection: 'desc' }, + templateTimelineId: '46a50505-0a48-49cb-9ab2-d15d683efa3b', + templateTimelineVersion: 1, + created: 1596473742379, + createdBy: 'elastic', + updated: 1596473909169, + updatedBy: 'elastic', + dateRange: { start: '2020-08-02T16:55:22.160Z', end: '2020-08-03T16:55:22.161Z' }, + savedQueryId: null, + eventNotes: [], + globalNotes: [ + { + noteId: '358f45c0-d5aa-11ea-9b6d-53d136d390dc', + version: 'WzQzOCwxXQ==', + note: 'I have a global note', + timelineId: 'cab434d0-d26c-11ea-b887-3b103296472a', + created: 1596473760688, + createdBy: 'elastic', + updated: 1596473760688, + updatedBy: 'elastic', + }, + ], + pinnedEventIds: [], + }) + ); + }; + + afterEach(async () => { + await deleteAllTimelines(es); + }); + + it("if it doesn't exists", async () => { + const { body } = await supertest + .post(`${TIMELINE_IMPORT_URL}`) + .set('kbn-xsrf', 'true') + .attach('file', getTimelineTemplate(), 'timelines.ndjson') + .expect(200); + expect(body).to.eql({ + errors: [], + success: true, + success_count: 1, + timelines_installed: 1, + timelines_updated: 0, + }); + }); + }); + + describe('Updating a timeline template', () => { + const getTimelineTemplate = (templateTimelineVersion: number) => { + return Buffer.from( + JSON.stringify({ + savedObjectId: 'cab434d0-d26c-11ea-b887-3b103296472a', + version: 'WzQ0NSwxXQ==', + columns: [ + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: '@timestamp', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'message', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'event.category', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'event.action', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'host.name', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'source.ip', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'destination.ip', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'user.name', + searchable: null, + }, + ], + dataProviders: [], + description: 'desc', + eventType: 'all', + filters: [], + kqlMode: 'filter', + timelineType: 'template', + kqlQuery: { filterQuery: null }, + title: 'my t template', + sort: { columnId: '@timestamp', sortDirection: 'desc' }, + templateTimelineId: '46a50505-0a48-49cb-9ab2-d15d683efa3b', + templateTimelineVersion, + created: 1596473742379, + createdBy: 'elastic', + updated: 1596473909169, + updatedBy: 'elastic', + dateRange: { start: '2020-08-02T16:55:22.160Z', end: '2020-08-03T16:55:22.161Z' }, + savedQueryId: null, + eventNotes: [], + globalNotes: [ + { + noteId: '358f45c0-d5aa-11ea-9b6d-53d136d390dc', + version: 'WzQzOCwxXQ==', + note: 'I have a global note', + timelineId: 'cab434d0-d26c-11ea-b887-3b103296472a', + created: 1596473760688, + createdBy: 'elastic', + updated: 1596473760688, + updatedBy: 'elastic', + }, + ], + pinnedEventIds: [], + }) + ); + }; + + afterEach(async () => { + await deleteAllTimelines(es); + }); + + it("if it doesn't exists", async () => { + await supertest + .post(`${TIMELINE_IMPORT_URL}`) + .set('kbn-xsrf', 'true') + .attach('file', getTimelineTemplate(1), 'timelines.ndjson') + .expect(200); + const { body } = await supertest + .post(`${TIMELINE_IMPORT_URL}`) + .set('kbn-xsrf', 'true') + .attach('file', getTimelineTemplate(2), 'timelines.ndjson') + .expect(200); + expect(body).to.eql({ + errors: [], + success: true, + success_count: 1, + timelines_installed: 0, + timelines_updated: 1, + }); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/index.ts b/x-pack/test/detection_engine_api_integration/basic/tests/index.ts index a480e63ff4a92..09feb7ac5e0ec 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/index.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/index.ts @@ -28,5 +28,6 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./patch_rules')); loadTestFile(require.resolve('./query_signals')); loadTestFile(require.resolve('./open_close_signals')); + loadTestFile(require.resolve('./import_timelines')); }); }; diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/install_prepackaged_timelines.ts b/x-pack/test/detection_engine_api_integration/basic/tests/install_prepackaged_timelines.ts new file mode 100644 index 0000000000000..556217877968b --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/basic/tests/install_prepackaged_timelines.ts @@ -0,0 +1,87 @@ +/* + * 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 expect from '@kbn/expect'; + +import { TIMELINE_PREPACKAGED_URL } from '../../../../plugins/security_solution/common/constants'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { + createSignalsIndex, + deleteAllAlerts, + deleteAllTimelines, + deleteSignalsIndex, + waitFor, +} from '../../utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + const es = getService('es'); + + describe('install_prepackaged_timelines', () => { + describe('creating prepackaged rules', () => { + beforeEach(async () => { + await createSignalsIndex(supertest); + }); + + afterEach(async () => { + await deleteSignalsIndex(supertest); + await deleteAllAlerts(es); + await deleteAllTimelines(es); + }); + + it('should contain timelines_installed, and timelines_updated', async () => { + const { body } = await supertest + .put(TIMELINE_PREPACKAGED_URL) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(Object.keys(body)).to.eql(['timelines_installed', 'timelines_updated']); + }); + + it('should create the prepackaged timelines and return a count greater than zero', async () => { + const { body } = await supertest + .put(TIMELINE_PREPACKAGED_URL) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(body.timelines_installed).to.be.greaterThan(0); + }); + + it('should create the prepackaged timelines and the timelines_updated is of size zero', async () => { + const { body } = await supertest + .put(TIMELINE_PREPACKAGED_URL) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(body.timelines_updated).to.eql(0); + }); + + it('should be possible to call the API twice and the second time the number of timelines installed should be zero', async () => { + await supertest.put(TIMELINE_PREPACKAGED_URL).set('kbn-xsrf', 'true').send().expect(200); + + await waitFor(async () => { + const { body } = await supertest + .get(`${TIMELINE_PREPACKAGED_URL}/_status`) + .set('kbn-xsrf', 'true') + .expect(200); + return body.timelines_not_installed === 0; + }); + + const { body } = await supertest + .put(TIMELINE_PREPACKAGED_URL) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(body.timelines_installed).to.eql(0); + }); + }); + }); +};