[SIEM] Add api integration test + fix bug to default value in timeline (#39577)

* bug when persited timeline does not have all the data wanted on the UI

* add api integration testing for timelien

* review I

* update snapshot

* again update snapshots for stat_item
This commit is contained in:
Xavier Mouligneau 2019-06-27 20:26:00 -04:00 committed by GitHub
parent 4b49db591d
commit d74f4f7f9f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 676 additions and 88 deletions

View file

@ -8965,12 +8965,6 @@
}
},
"defaultValue": null
},
{
"name": "version",
"description": "",
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"defaultValue": null
}
],
"type": { "kind": "SCALAR", "name": "Boolean", "ofType": null },

View file

@ -1978,8 +1978,6 @@ export interface PersistNoteMutationArgs {
}
export interface DeleteNoteMutationArgs {
id: string[];
version?: string | null;
}
export interface DeleteNoteByTimelineIdMutationArgs {
timelineId: string;

View file

@ -15,6 +15,7 @@ import {
import { KueryFilterQuery, SerializedFilterQuery } from '../model';
import { KqlMode, TimelineModel } from './model';
import { TimelineResult } from '../../graphql/types';
const actionCreator = actionCreatorFactory('x-pack/siem/local/timeline');
@ -76,7 +77,7 @@ export const updateTimeline = actionCreator<{
export const addTimeline = actionCreator<{
id: string;
timeline: TimelineModel;
timeline: TimelineResult;
}>('ADD_TIMELINE');
export const startTimelineSaving = actionCreator<{

View file

@ -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 { getOr, omit, uniq, isEmpty, isEqualWith } from 'lodash/fp';
import { getOr, omit, uniq, isEmpty, isEqualWith, defaultsDeep, pickBy, isNil } from 'lodash/fp';
import { ColumnHeader } from '../../components/timeline/body/column_headers/column_header';
import { getColumnWidthFromType } from '../../components/timeline/body/helpers';
@ -17,6 +17,7 @@ import { KueryFilterQuery, SerializedFilterQuery } from '../model';
import { KqlMode, timelineDefaults, TimelineModel } from './model';
import { TimelineById, TimelineState } from './types';
import { TimelineResult } from '../../graphql/types';
const EMPTY_TIMELINE_BY_ID: TimelineById = {}; // stable reference
@ -102,12 +103,31 @@ export const addTimelineNoteToEvent = ({
};
};
interface AddTimelineParams {
id: string;
timeline: TimelineResult;
}
/**
* Add a saved object timeline to the store
* and default the value to what need to be if values are null
*/
export const addTimelineToStore = ({ id, timeline }: AddTimelineParams): TimelineById => ({
// TODO: revisit this when we support multiple timelines
[id]: {
...defaultsDeep(timelineDefaults, pickBy(v => !isNil(v), timeline)),
id: timeline.savedObjectId || '',
show: true,
},
});
interface AddNewTimelineParams {
columns: ColumnHeader[];
id: string;
show?: boolean;
timelineById: TimelineById;
}
/** Adds a new `Timeline` to the provided collection of `TimelineById` */
export const addNewTimeline = ({
columns,

View file

@ -24,6 +24,7 @@ import { defaultHeaders } from '../../mock';
import {
addNewTimeline,
addTimelineProvider,
addTimelineToStore,
applyDeltaToTimelineColumnWidth,
removeTimelineColumn,
removeTimelineProvider,
@ -97,6 +98,99 @@ const timelineByIdMock: TimelineById = {
const columnsMock: ColumnHeader[] = [defaultHeaders[0], defaultHeaders[1], defaultHeaders[2]];
describe('Timeline', () => {
describe('#add saved object Timeline to store ', () => {
test('should return a timelineModel with default value and not just a timelineResult ', () => {
const update = addTimelineToStore({
id: 'foo',
timeline: {
savedObjectId: 'superUniqueId',
title: 'saved object timeline',
version: 'doNotForgetVersion',
},
});
expect(update).toEqual({
foo: {
columns: [
{
columnHeaderType: 'not-filtered',
id: '@timestamp',
width: 240,
},
{
columnHeaderType: 'not-filtered',
id: 'message',
width: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'event.category',
width: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'event.action',
width: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'host.name',
width: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'source.ip',
width: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'destination.ip',
width: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'user.name',
width: 180,
},
],
dataProviders: [],
dateRange: {
end: 0,
start: 0,
},
description: '',
eventIdToNoteIds: {},
highlightedDropAndProviderId: '',
historyIds: [],
id: 'superUniqueId',
isFavorite: false,
isLive: false,
isLoading: false,
isSaving: false,
itemsPerPage: 25,
itemsPerPageOptions: [10, 25, 50, 100],
kqlMode: 'filter',
kqlQuery: {
filterQuery: null,
filterQueryDraft: null,
},
noteIds: [],
pinnedEventIds: {},
pinnedEventsSaveObject: {},
savedObjectId: 'superUniqueId',
show: true,
sort: {
columnId: '@timestamp',
sortDirection: 'desc',
},
title: 'saved object timeline',
version: 'doNotForgetVersion',
width: 1100,
},
});
});
});
describe('#addNewTimeline', () => {
test('should return a new reference and not the same reference', () => {
const update = addNewTimeline({

View file

@ -3,10 +3,8 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { reducerWithInitialState } from 'typescript-fsa-reducers';
import { DEFAULT_TIMELINE_WIDTH } from '../../components/timeline/body/helpers';
import {
addTimeline,
addHistory,
@ -53,6 +51,7 @@ import {
addTimelineNote,
addTimelineNoteToEvent,
addTimelineProvider,
addTimelineToStore,
applyDeltaToCurrentWidth,
applyDeltaToTimelineColumnWidth,
applyKqlFilterQueryDraft,
@ -96,28 +95,7 @@ export const initialTimelineState: TimelineState = {
export const timelineReducer = reducerWithInitialState(initialTimelineState)
.case(addTimeline, (state, { id, timeline }) => ({
...state,
timelineById: {
// As right now, We are not managing multiple timeline
// for now simplification, we do not need the line below
// ...state.timelineById,
[id]: {
...timeline,
highlightedDropAndProviderId: '',
historyIds: [],
isLive: false,
isLoading: true,
itemsPerPage: 25,
itemsPerPageOptions: [10, 25, 50, 100],
id: timeline.savedObjectId || '',
dateRange: {
start: 0,
end: 0,
},
show: true,
width: DEFAULT_TIMELINE_WIDTH,
isSaving: false,
},
},
timelineById: addTimelineToStore({ id, timeline }),
}))
.case(createTimeline, (state, { id, show, columns }) => ({
...state,

View file

@ -75,7 +75,7 @@ export const noteSchema = gql`
extend type Mutation {
"Persists a note"
persistNote(noteId: ID, version: String, note: NoteInput!): ResponseNote!
deleteNote(id: [ID!]!, version: String):Boolean
deleteNote(id: [ID!]!):Boolean
deleteNoteByTimelineId(timelineId: ID!, version: String):Boolean
}
`;

View file

@ -2007,8 +2007,6 @@ export interface PersistNoteMutationArgs {
}
export interface DeleteNoteMutationArgs {
id: string[];
version?: string | null;
}
export interface DeleteNoteByTimelineIdMutationArgs {
timelineId: string;
@ -7116,8 +7114,6 @@ export namespace MutationResolvers {
> = Resolver<R, Parent, Context, DeleteNoteArgs>;
export interface DeleteNoteArgs {
id: string[];
version?: string | null;
}
export type DeleteNoteByTimelineIdResolver<

View file

@ -104,21 +104,24 @@ export class Note {
version: string | null,
note: SavedNote
): Promise<ResponseNote> {
let timelineVersionSavedObject = null;
try {
if (note.timelineId == null) {
const timelineResult = convertSavedObjectToSavedTimeline(
await this.libs.savedObjects
.getScopedSavedObjectsClient(request[internalFrameworkRequest])
.create(
timelineSavedObjectType,
pickSavedTimeline(null, {}, request[internalFrameworkRequest].auth || null)
)
);
note.timelineId = timelineResult.savedObjectId;
timelineVersionSavedObject = timelineResult.version;
}
if (noteId == null) {
const timelineVersionSavedObject =
note.timelineId == null
? await (async () => {
const timelineResult = convertSavedObjectToSavedTimeline(
await this.libs.savedObjects
.getScopedSavedObjectsClient(request[internalFrameworkRequest])
.create(
timelineSavedObjectType,
pickSavedTimeline(null, {}, request[internalFrameworkRequest].auth || null)
)
);
note.timelineId = timelineResult.savedObjectId;
return timelineResult.version;
})()
: null;
// Create new note
return {
code: 200,
@ -134,6 +137,7 @@ export class Note {
),
};
}
// Update new note
return {
code: 200,

View file

@ -97,46 +97,52 @@ export class PinnedEvent {
eventId: string,
timelineId: string | null
): Promise<PinnedEventSavedObject | null> {
let timelineVersionSavedObject = null;
try {
if (timelineId == null) {
const timelineResult = convertSavedObjectToSavedTimeline(
await this.libs.savedObjects
.getScopedSavedObjectsClient(request[internalFrameworkRequest])
.create(
timelineSavedObjectType,
pickSavedTimeline(null, {}, request[internalFrameworkRequest].auth || null)
)
);
timelineId = timelineResult.savedObjectId;
timelineVersionSavedObject = timelineResult.version;
}
if (pinnedEventId == null) {
const allPinnedEventId = await this.getAllPinnedEventsByTimelineId(request, timelineId);
const isPinnedAlreadyExisting = allPinnedEventId.filter(
pinnedEvent => pinnedEvent.eventId === eventId
);
if (isPinnedAlreadyExisting.length === 0) {
const savedPinnedEvent: SavedPinnedEvent = {
eventId,
timelineId,
};
// create Pinned Event on Timeline
return convertSavedObjectToSavedPinnedEvent(
await this.libs.savedObjects
.getScopedSavedObjectsClient(request[internalFrameworkRequest])
.create(
pinnedEventSavedObjectType,
pickSavedPinnedEvent(
pinnedEventId,
savedPinnedEvent,
request[internalFrameworkRequest].auth || null
)
),
timelineVersionSavedObject != null ? timelineVersionSavedObject : undefined
const timelineVersionSavedObject =
timelineId == null
? await (async () => {
const timelineResult = convertSavedObjectToSavedTimeline(
await this.libs.savedObjects
.getScopedSavedObjectsClient(request[internalFrameworkRequest])
.create(
timelineSavedObjectType,
pickSavedTimeline(null, {}, request[internalFrameworkRequest].auth || null)
)
);
timelineId = timelineResult.savedObjectId;
return timelineResult.version;
})()
: null;
if (timelineId != null) {
const allPinnedEventId = await this.getAllPinnedEventsByTimelineId(request, timelineId);
const isPinnedAlreadyExisting = allPinnedEventId.filter(
pinnedEvent => pinnedEvent.eventId === eventId
);
if (isPinnedAlreadyExisting.length === 0) {
const savedPinnedEvent: SavedPinnedEvent = {
eventId,
timelineId,
};
// create Pinned Event on Timeline
return convertSavedObjectToSavedPinnedEvent(
await this.libs.savedObjects
.getScopedSavedObjectsClient(request[internalFrameworkRequest])
.create(
pinnedEventSavedObjectType,
pickSavedPinnedEvent(
pinnedEventId,
savedPinnedEvent,
request[internalFrameworkRequest].auth || null
)
),
timelineVersionSavedObject != null ? timelineVersionSavedObject : undefined
);
}
return isPinnedAlreadyExisting[0];
}
return isPinnedAlreadyExisting[0];
throw new Error('You can NOT pinned event without a timelineID');
}
// Delete Pinned Event on Timeline
await this.deletePinnedEventOnTimeline(request, [pinnedEventId]);

View file

@ -15,6 +15,9 @@ export default function ({ loadTestFile }) {
loadTestFile(require.resolve('./network_dns'));
loadTestFile(require.resolve('./network_top_n_flow'));
loadTestFile(require.resolve('./overview_host'));
loadTestFile(require.resolve('./saved_objects/notes'));
loadTestFile(require.resolve('./saved_objects/pinned_events'));
loadTestFile(require.resolve('./saved_objects/timeline'));
loadTestFile(require.resolve('./sources'));
loadTestFile(require.resolve('./overview_network'));
loadTestFile(require.resolve('./timeline'));

View file

@ -0,0 +1,106 @@
/*
* 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 gql from 'graphql-tag';
import { persistTimelineNoteMutation } from '../../../../../legacy/plugins/siem/public/containers/timeline/notes/persist.gql_query';
import { KbnTestProvider } from '../types';
const notesPersistenceTests: KbnTestProvider = ({ getService }) => {
const esArchiver = getService('esArchiver');
const client = getService('siemGraphQLClient');
describe('Note - Saved Objects', () => {
beforeEach(() => esArchiver.load('empty_kibana'));
afterEach(() => esArchiver.unload('empty_kibana'));
describe('create a note', () => {
it('should return a timelineId, timelineVersion, noteId and version', async () => {
const myNote = 'world test';
const response = await client.mutate<any>({
mutation: persistTimelineNoteMutation,
variables: {
noteId: null,
version: null,
note: { note: myNote, timelineId: null },
},
});
const { note, noteId, timelineId, timelineVersion, version } =
response.data && response.data.persistNote.note;
expect(note).to.be(myNote);
expect(noteId).to.not.be.empty();
expect(timelineId).to.not.be.empty();
expect(timelineVersion).to.not.be.empty();
expect(version).to.not.be.empty();
});
it('if noteId exist update note and return existing noteId and new version', async () => {
const myNote = 'world test';
const response = await client.mutate<any>({
mutation: persistTimelineNoteMutation,
variables: {
noteId: null,
version: null,
note: { note: myNote, timelineId: null },
},
});
const { noteId, timelineId, version } = response.data && response.data.persistNote.note;
const myNewNote = 'new world test';
const responseToTest = await client.mutate<any>({
mutation: persistTimelineNoteMutation,
variables: {
noteId,
version,
note: { note: myNewNote, timelineId },
},
});
expect(responseToTest.data!.persistNote.note.note).to.be(myNewNote);
expect(responseToTest.data!.persistNote.note.noteId).to.be(noteId);
expect(responseToTest.data!.persistNote.note.version).to.not.be.eql(version);
});
});
describe('Delete a note', () => {
it('one note', async () => {
const myNote = 'world test';
const response = await client.mutate<any>({
mutation: persistTimelineNoteMutation,
variables: {
noteId: null,
version: null,
note: { note: myNote, timelineId: null },
},
});
const { noteId } = response.data && response.data.persistNote.note;
const responseToTest = await client.mutate<any>({
mutation: deleteNoteMutation,
variables: {
id: [noteId],
},
});
expect(responseToTest.data!.deleteNote).to.be(true);
});
});
});
};
// eslint-disable-next-line import/no-default-export
export default notesPersistenceTests;
const deleteNoteMutation = gql`
mutation DeleteNoteMutation($id: [ID!]!) {
deleteNote(id: $id)
}
`;

View file

@ -0,0 +1,68 @@
/*
* 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 { persistTimelinePinnedEventMutation } from '../../../../../legacy/plugins/siem/public/containers/timeline/pinned_event/persist.gql_query';
import { KbnTestProvider } from '../types';
const pinnedEventsPersistenceTests: KbnTestProvider = ({ getService }) => {
const esArchiver = getService('esArchiver');
const client = getService('siemGraphQLClient');
describe('Pinned Events - Saved Objects', () => {
beforeEach(() => esArchiver.load('empty_kibana'));
afterEach(() => esArchiver.unload('empty_kibana'));
describe('Pinned an event', () => {
it('return a timelineId, timelineVersion, pinnedEventId and version', async () => {
const response = await client.mutate<any>({
mutation: persistTimelinePinnedEventMutation,
variables: {
pinnedEventId: null,
eventId: 'bv4QSGsB9v5HJNSH-7fi',
},
});
const { eventId, pinnedEventId, timelineId, timelineVersion, version } =
response.data && response.data.persistPinnedEventOnTimeline;
expect(eventId).to.be('bv4QSGsB9v5HJNSH-7fi');
expect(pinnedEventId).to.not.be.empty();
expect(timelineId).to.not.be.empty();
expect(timelineVersion).to.not.be.empty();
expect(version).to.not.be.empty();
});
});
describe('Unpinned an event', () => {
it('return null', async () => {
const response = await client.mutate<any>({
mutation: persistTimelinePinnedEventMutation,
variables: {
pinnedEventId: null,
eventId: 'bv4QSGsB9v5HJNSH-7fi',
},
});
const { eventId, pinnedEventId } =
response.data && response.data.persistPinnedEventOnTimeline;
const responseToTest = await client.mutate<any>({
mutation: persistTimelinePinnedEventMutation,
variables: {
pinnedEventId,
eventId,
},
});
expect(responseToTest.data!.persistPinnedEventOnTimeline).to.be(null);
});
});
});
};
// eslint-disable-next-line import/no-default-export
export default pinnedEventsPersistenceTests;

View file

@ -0,0 +1,320 @@
/*
* 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.
*/
/*
* 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 ApolloClient from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { deleteTimelineMutation } from '../../../../../legacy/plugins/siem/public/containers/timeline/delete/persist.gql_query';
import { persistTimelineFavoriteMutation } from '../../../../../legacy/plugins/siem/public/containers/timeline/favorite/persist.gql_query';
import { persistTimelineMutation } from '../../../../../legacy/plugins/siem/public/containers/timeline/persist.gql_query';
import { TimelineResult } from '../../../../../legacy/plugins/siem/public/graphql/types';
import { KbnTestProvider } from '../types';
const timelinePersistenceTests: KbnTestProvider = ({ getService }) => {
const esArchiver = getService('esArchiver');
const client = getService('siemGraphQLClient');
describe('Timeline - Saved Objects', () => {
beforeEach(() => esArchiver.load('empty_kibana'));
afterEach(() => esArchiver.unload('empty_kibana'));
describe('Persist a timeline', () => {
it('Create a timeline just with a title', async () => {
const titleToSaved = 'hello title';
const response = await createBasicTimeline(client, titleToSaved);
const { savedObjectId, title, version } =
response.data && response.data.persistTimeline.timeline;
expect(title).to.be(titleToSaved);
expect(savedObjectId).to.not.be.empty();
expect(version).to.not.be.empty();
});
it('Create a timeline with a full object', async () => {
const timelineObject = {
columns: [
{
columnHeaderType: 'not-filtered',
indexes: null,
id: '@timestamp',
name: null,
searchable: null,
},
{
columnHeaderType: 'not-filtered',
indexes: null,
id: '_index',
name: null,
searchable: null,
},
{
columnHeaderType: 'not-filtered',
indexes: null,
id: 'message',
name: null,
searchable: null,
},
{
columnHeaderType: 'not-filtered',
indexes: null,
id: 'event.category',
name: null,
searchable: null,
},
{
columnHeaderType: 'not-filtered',
indexes: null,
id: 'event.action',
name: null,
searchable: null,
},
{
columnHeaderType: 'not-filtered',
indexes: null,
id: 'host.name',
name: null,
searchable: null,
},
{
columnHeaderType: 'not-filtered',
indexes: null,
id: 'source.ip',
name: null,
searchable: null,
},
{
columnHeaderType: 'not-filtered',
indexes: null,
id: 'destination.ip',
name: null,
searchable: null,
},
{
columnHeaderType: 'not-filtered',
indexes: null,
id: 'user.name',
name: null,
searchable: null,
},
],
dataProviders: [
{
id: 'hosts-table-hostName-zeek-iowa',
name: 'zeek-iowa',
enabled: true,
excluded: false,
kqlQuery: '',
queryMatch: {
field: 'host.name',
displayField: null,
value: 'zeek-iowa',
displayValue: null,
operator: ':',
},
and: [],
},
],
description: 'some description',
kqlMode: 'filter',
kqlQuery: {
filterQuery: {
kuery: {
kind: 'kuery',
expression: 'network.community_id : "1:pNuEtJ941SagdsAnFculFyREvXw=" ',
},
serializedQuery:
'{"bool":{"should":[{"match_phrase":{"network.community_id":"1:pNuEtJ941SagdsAnFculFyREvXw="}}],"minimum_should_match":1}}',
},
},
title: 'some title',
dateRange: { start: 1560195800755, end: 1560282200756 },
sort: { columnId: '@timestamp', sortDirection: 'desc' },
};
const response = await client.mutate<any>({
mutation: persistTimelineMutation,
variables: {
timelineId: null,
version: null,
timeline: timelineObject,
},
});
const {
columns,
dataProviders,
dateRange,
description,
kqlMode,
kqlQuery,
savedObjectId,
sort,
title,
version,
} = response.data && omitTypenameInTimeline(response.data.persistTimeline.timeline);
expect(columns.map((col: { id: string }) => col.id)).to.eql(
timelineObject.columns.map(col => col.id)
);
expect(dataProviders).to.eql(timelineObject.dataProviders);
expect(dateRange).to.eql(timelineObject.dateRange);
expect(description).to.be(timelineObject.description);
expect(kqlMode).to.be(timelineObject.kqlMode);
expect(kqlQuery).to.eql(timelineObject.kqlQuery);
expect(savedObjectId).to.not.be.empty();
expect(sort).to.eql(timelineObject.sort);
expect(title).to.be(timelineObject.title);
expect(version).to.not.be.empty();
});
it('Update a timeline with a new title', async () => {
const titleToSaved = 'hello title';
const response = await createBasicTimeline(client, titleToSaved);
const { savedObjectId, version } = response.data && response.data.persistTimeline.timeline;
const newTitle = 'new title';
const responseToTest = await client.mutate<any>({
mutation: persistTimelineMutation,
variables: {
timelineId: savedObjectId,
version,
timeline: {
title: newTitle,
},
},
});
expect(responseToTest.data!.persistTimeline.timeline.savedObjectId).to.be(savedObjectId);
expect(responseToTest.data!.persistTimeline.timeline.title).to.be(newTitle);
expect(responseToTest.data!.persistTimeline.timeline.version).to.not.be.eql(version);
});
});
describe('Persist favorite', () => {
it('to an existing timeline', async () => {
const titleToSaved = 'hello title';
const response = await createBasicTimeline(client, titleToSaved);
const { savedObjectId, version } = response.data && response.data.persistTimeline.timeline;
const responseToTest = await client.mutate<any>({
mutation: persistTimelineFavoriteMutation,
variables: {
timelineId: savedObjectId,
},
});
expect(responseToTest.data!.persistFavorite.savedObjectId).to.be(savedObjectId);
expect(responseToTest.data!.persistFavorite.favorite.length).to.be(1);
expect(responseToTest.data!.persistFavorite.version).to.not.be.eql(version);
});
it('to Unfavorite an existing timeline', async () => {
const titleToSaved = 'hello title';
const response = await createBasicTimeline(client, titleToSaved);
const { savedObjectId, version } = response.data && response.data.persistTimeline.timeline;
await client.mutate<any>({
mutation: persistTimelineFavoriteMutation,
variables: {
timelineId: savedObjectId,
},
});
const responseToTest = await client.mutate<any>({
mutation: persistTimelineFavoriteMutation,
variables: {
timelineId: savedObjectId,
},
});
expect(responseToTest.data!.persistFavorite.savedObjectId).to.be(savedObjectId);
expect(responseToTest.data!.persistFavorite.favorite).to.be.empty();
expect(responseToTest.data!.persistFavorite.version).to.not.be.eql(version);
});
it('to a timeline without a timelineId', async () => {
const response = await client.mutate<any>({
mutation: persistTimelineFavoriteMutation,
variables: {
timelineId: null,
},
});
expect(response.data!.persistFavorite.savedObjectId).to.not.be.empty();
expect(response.data!.persistFavorite.favorite.length).to.be(1);
expect(response.data!.persistFavorite.version).to.not.be.empty();
});
});
describe('Delete', () => {
it('one timeline', async () => {
const titleToSaved = 'hello title';
const response = await createBasicTimeline(client, titleToSaved);
const { savedObjectId } = response.data && response.data.persistTimeline.timeline;
const responseToTest = await client.mutate<any>({
mutation: deleteTimelineMutation,
variables: {
id: [savedObjectId],
},
});
expect(responseToTest.data!.deleteTimeline).to.be(true);
});
it('multiple timeline', async () => {
const titleToSaved = 'hello title';
const response1 = await createBasicTimeline(client, titleToSaved);
const savedObjectId1 =
response1.data && response1.data.persistTimeline.timeline
? response1.data.persistTimeline.timeline.savedObjectId
: '';
const response2 = await createBasicTimeline(client, titleToSaved);
const savedObjectId2 =
response2.data && response2.data.persistTimeline.timeline
? response2.data.persistTimeline.timeline.savedObjectId
: '';
const responseToTest = await client.mutate<any>({
mutation: deleteTimelineMutation,
variables: {
id: [savedObjectId1, savedObjectId2],
},
});
expect(responseToTest.data!.deleteTimeline).to.be(true);
});
});
});
};
// eslint-disable-next-line import/no-default-export
export default timelinePersistenceTests;
const omitTypename = (key: string, value: keyof TimelineResult) =>
key === '__typename' ? undefined : value;
const omitTypenameInTimeline = (timeline: TimelineResult) =>
JSON.parse(JSON.stringify(timeline), omitTypename);
const createBasicTimeline = async (client: ApolloClient<InMemoryCache>, titleToSaved: string) =>
await client.mutate<any>({
mutation: persistTimelineMutation,
variables: {
timelineId: null,
version: null,
timeline: {
title: titleToSaved,
},
},
});