[Security Solution][Case] Attach alerts to cases: Tests (#86305)
Co-authored-by: Steph Milovic <stephanie.milovic@elastic.co> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
5c719e9ad9
commit
a1931acdc5
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* 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 { CaseStatuses } from '../../../common/api';
|
||||||
|
import { createMockSavedObjectsRepository } from '../../routes/api/__fixtures__';
|
||||||
|
import { createCaseClientWithMockSavedObjectsClient } from '../mocks';
|
||||||
|
|
||||||
|
describe('updateAlertsStatus', () => {
|
||||||
|
describe('happy path', () => {
|
||||||
|
test('it update the status of the alert correctly', async () => {
|
||||||
|
const savedObjectsClient = createMockSavedObjectsRepository();
|
||||||
|
|
||||||
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
|
await caseClient.client.updateAlertsStatus({
|
||||||
|
ids: ['alert-id-1'],
|
||||||
|
status: CaseStatuses.closed,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(caseClient.services.alertsService.updateAlertsStatus).toHaveBeenCalledWith({
|
||||||
|
ids: ['alert-id-1'],
|
||||||
|
index: '.siem-signals',
|
||||||
|
request: {},
|
||||||
|
status: CaseStatuses.closed,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('unhappy path', () => {
|
||||||
|
test('it throws when missing securitySolutionClient', async () => {
|
||||||
|
expect.assertions(3);
|
||||||
|
|
||||||
|
const savedObjectsClient = createMockSavedObjectsRepository();
|
||||||
|
|
||||||
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({
|
||||||
|
savedObjectsClient,
|
||||||
|
omitFromContext: ['securitySolution'],
|
||||||
|
});
|
||||||
|
caseClient.client
|
||||||
|
.updateAlertsStatus({
|
||||||
|
ids: ['alert-id-1'],
|
||||||
|
status: CaseStatuses.closed,
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
expect(e).not.toBeNull();
|
||||||
|
expect(e.isBoom).toBe(true);
|
||||||
|
expect(e.output.statusCode).toBe(404);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -43,7 +43,7 @@ describe('create', () => {
|
||||||
caseSavedObject: mockCases,
|
caseSavedObject: mockCases,
|
||||||
caseConfigureSavedObject: mockCaseConfigure,
|
caseConfigureSavedObject: mockCaseConfigure,
|
||||||
});
|
});
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
const res = await caseClient.client.create({ theCase: postCase });
|
const res = await caseClient.client.create({ theCase: postCase });
|
||||||
|
|
||||||
expect(res).toEqual({
|
expect(res).toEqual({
|
||||||
|
@ -120,7 +120,7 @@ describe('create', () => {
|
||||||
const savedObjectsClient = createMockSavedObjectsRepository({
|
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||||
caseSavedObject: mockCases,
|
caseSavedObject: mockCases,
|
||||||
});
|
});
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
const res = await caseClient.client.create({ theCase: postCase });
|
const res = await caseClient.client.create({ theCase: postCase });
|
||||||
|
|
||||||
expect(res).toEqual({
|
expect(res).toEqual({
|
||||||
|
@ -165,7 +165,10 @@ describe('create', () => {
|
||||||
const savedObjectsClient = createMockSavedObjectsRepository({
|
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||||
caseSavedObject: mockCases,
|
caseSavedObject: mockCases,
|
||||||
});
|
});
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient, true);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({
|
||||||
|
savedObjectsClient,
|
||||||
|
badAuth: true,
|
||||||
|
});
|
||||||
const res = await caseClient.client.create({ theCase: postCase });
|
const res = await caseClient.client.create({ theCase: postCase });
|
||||||
|
|
||||||
expect(res).toEqual({
|
expect(res).toEqual({
|
||||||
|
@ -213,7 +216,7 @@ describe('create', () => {
|
||||||
const savedObjectsClient = createMockSavedObjectsRepository({
|
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||||
caseSavedObject: mockCases,
|
caseSavedObject: mockCases,
|
||||||
});
|
});
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
caseClient.client
|
caseClient.client
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
.create({ theCase: postCase })
|
.create({ theCase: postCase })
|
||||||
|
@ -240,7 +243,7 @@ describe('create', () => {
|
||||||
const savedObjectsClient = createMockSavedObjectsRepository({
|
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||||
caseSavedObject: mockCases,
|
caseSavedObject: mockCases,
|
||||||
});
|
});
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
caseClient.client
|
caseClient.client
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
.create({ theCase: postCase })
|
.create({ theCase: postCase })
|
||||||
|
@ -267,7 +270,7 @@ describe('create', () => {
|
||||||
const savedObjectsClient = createMockSavedObjectsRepository({
|
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||||
caseSavedObject: mockCases,
|
caseSavedObject: mockCases,
|
||||||
});
|
});
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
caseClient.client
|
caseClient.client
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
.create({ theCase: postCase })
|
.create({ theCase: postCase })
|
||||||
|
@ -289,7 +292,7 @@ describe('create', () => {
|
||||||
const savedObjectsClient = createMockSavedObjectsRepository({
|
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||||
caseSavedObject: mockCases,
|
caseSavedObject: mockCases,
|
||||||
});
|
});
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
caseClient.client
|
caseClient.client
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
.create({ theCase: postCase })
|
.create({ theCase: postCase })
|
||||||
|
@ -317,7 +320,7 @@ describe('create', () => {
|
||||||
const savedObjectsClient = createMockSavedObjectsRepository({
|
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||||
caseSavedObject: mockCases,
|
caseSavedObject: mockCases,
|
||||||
});
|
});
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
caseClient.client
|
caseClient.client
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
.create({ theCase: postCase })
|
.create({ theCase: postCase })
|
||||||
|
@ -349,7 +352,7 @@ describe('create', () => {
|
||||||
const savedObjectsClient = createMockSavedObjectsRepository({
|
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||||
caseSavedObject: mockCases,
|
caseSavedObject: mockCases,
|
||||||
});
|
});
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
caseClient.client.create({ theCase: postCase }).catch((e) => {
|
caseClient.client.create({ theCase: postCase }).catch((e) => {
|
||||||
expect(e).not.toBeNull();
|
expect(e).not.toBeNull();
|
||||||
expect(e.isBoom).toBe(true);
|
expect(e.isBoom).toBe(true);
|
||||||
|
@ -375,7 +378,7 @@ describe('create', () => {
|
||||||
const savedObjectsClient = createMockSavedObjectsRepository({
|
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||||
caseSavedObject: mockCases,
|
caseSavedObject: mockCases,
|
||||||
});
|
});
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
|
|
||||||
caseClient.client.create({ theCase: postCase }).catch((e) => {
|
caseClient.client.create({ theCase: postCase }).catch((e) => {
|
||||||
expect(e).not.toBeNull();
|
expect(e).not.toBeNull();
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
createMockSavedObjectsRepository,
|
createMockSavedObjectsRepository,
|
||||||
mockCaseNoConnectorId,
|
mockCaseNoConnectorId,
|
||||||
mockCases,
|
mockCases,
|
||||||
|
mockCaseComments,
|
||||||
} from '../../routes/api/__fixtures__';
|
} from '../../routes/api/__fixtures__';
|
||||||
import { createCaseClientWithMockSavedObjectsClient } from '../mocks';
|
import { createCaseClientWithMockSavedObjectsClient } from '../mocks';
|
||||||
|
|
||||||
|
@ -37,7 +38,7 @@ describe('update', () => {
|
||||||
caseSavedObject: mockCases,
|
caseSavedObject: mockCases,
|
||||||
});
|
});
|
||||||
|
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
const res = await caseClient.client.update({
|
const res = await caseClient.client.update({
|
||||||
caseClient: caseClient.client,
|
caseClient: caseClient.client,
|
||||||
cases: patchCases,
|
cases: patchCases,
|
||||||
|
@ -120,7 +121,7 @@ describe('update', () => {
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
const res = await caseClient.client.update({
|
const res = await caseClient.client.update({
|
||||||
caseClient: caseClient.client,
|
caseClient: caseClient.client,
|
||||||
cases: patchCases,
|
cases: patchCases,
|
||||||
|
@ -156,6 +157,61 @@ describe('update', () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('it change the status of case to in-progress correctly', async () => {
|
||||||
|
const patchCases = {
|
||||||
|
cases: [
|
||||||
|
{
|
||||||
|
id: 'mock-id-4',
|
||||||
|
status: CaseStatuses['in-progress'],
|
||||||
|
version: 'WzUsMV0=',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||||
|
caseSavedObject: mockCases,
|
||||||
|
});
|
||||||
|
|
||||||
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
|
const res = await caseClient.client.update({
|
||||||
|
caseClient: caseClient.client,
|
||||||
|
cases: patchCases,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res).toEqual([
|
||||||
|
{
|
||||||
|
closed_at: null,
|
||||||
|
closed_by: null,
|
||||||
|
comments: [],
|
||||||
|
connector: {
|
||||||
|
id: '123',
|
||||||
|
name: 'My connector',
|
||||||
|
type: ConnectorTypes.jira,
|
||||||
|
fields: {
|
||||||
|
issueType: 'Task',
|
||||||
|
parent: null,
|
||||||
|
priority: 'High',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created_at: '2019-11-25T22:32:17.947Z',
|
||||||
|
created_by: { email: 'testemail@elastic.co', full_name: 'elastic', username: 'elastic' },
|
||||||
|
description: 'Oh no, a bad meanie going LOLBins all over the place!',
|
||||||
|
id: 'mock-id-4',
|
||||||
|
external_service: null,
|
||||||
|
status: CaseStatuses['in-progress'],
|
||||||
|
tags: ['LOLBins'],
|
||||||
|
title: 'Another bad one',
|
||||||
|
totalComment: 0,
|
||||||
|
updated_at: '2019-11-25T21:54:48.952Z',
|
||||||
|
updated_by: { email: 'd00d@awesome.com', full_name: 'Awesome D00d', username: 'awesome' },
|
||||||
|
version: 'WzE3LDFd',
|
||||||
|
settings: {
|
||||||
|
syncAlerts: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
test('it updates a case without a connector.id', async () => {
|
test('it updates a case without a connector.id', async () => {
|
||||||
const patchCases = {
|
const patchCases = {
|
||||||
cases: [
|
cases: [
|
||||||
|
@ -171,7 +227,7 @@ describe('update', () => {
|
||||||
caseSavedObject: [mockCaseNoConnectorId],
|
caseSavedObject: [mockCaseNoConnectorId],
|
||||||
});
|
});
|
||||||
|
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
const res = await caseClient.client.update({
|
const res = await caseClient.client.update({
|
||||||
caseClient: caseClient.client,
|
caseClient: caseClient.client,
|
||||||
cases: patchCases,
|
cases: patchCases,
|
||||||
|
@ -227,7 +283,7 @@ describe('update', () => {
|
||||||
caseSavedObject: mockCases,
|
caseSavedObject: mockCases,
|
||||||
});
|
});
|
||||||
|
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
const res = await caseClient.client.update({
|
const res = await caseClient.client.update({
|
||||||
caseClient: caseClient.client,
|
caseClient: caseClient.client,
|
||||||
cases: patchCases,
|
cases: patchCases,
|
||||||
|
@ -270,6 +326,204 @@ describe('update', () => {
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('it updates alert status when the status is updated and syncAlerts=true', async () => {
|
||||||
|
const patchCases = {
|
||||||
|
cases: [
|
||||||
|
{
|
||||||
|
id: 'mock-id-1',
|
||||||
|
status: CaseStatuses.closed,
|
||||||
|
version: 'WzAsMV0=',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||||
|
caseSavedObject: mockCases,
|
||||||
|
caseCommentSavedObject: [{ ...mockCaseComments[3] }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
|
caseClient.client.updateAlertsStatus = jest.fn();
|
||||||
|
|
||||||
|
await caseClient.client.update({
|
||||||
|
caseClient: caseClient.client,
|
||||||
|
cases: patchCases,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(caseClient.client.updateAlertsStatus).toHaveBeenCalledWith({
|
||||||
|
ids: ['test-id'],
|
||||||
|
status: 'closed',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it does NOT updates alert status when the status is updated and syncAlerts=false', async () => {
|
||||||
|
const patchCases = {
|
||||||
|
cases: [
|
||||||
|
{
|
||||||
|
id: 'mock-id-1',
|
||||||
|
status: CaseStatuses.closed,
|
||||||
|
version: 'WzAsMV0=',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||||
|
caseSavedObject: [
|
||||||
|
{
|
||||||
|
...mockCases[0],
|
||||||
|
attributes: { ...mockCases[0].attributes, settings: { syncAlerts: false } },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
caseCommentSavedObject: [{ ...mockCaseComments[3] }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
|
caseClient.client.updateAlertsStatus = jest.fn();
|
||||||
|
|
||||||
|
await caseClient.client.update({
|
||||||
|
caseClient: caseClient.client,
|
||||||
|
cases: patchCases,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(caseClient.client.updateAlertsStatus).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it updates alert status when syncAlerts is turned on', async () => {
|
||||||
|
const patchCases = {
|
||||||
|
cases: [
|
||||||
|
{
|
||||||
|
id: 'mock-id-1',
|
||||||
|
settings: { syncAlerts: true },
|
||||||
|
version: 'WzAsMV0=',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||||
|
caseSavedObject: [
|
||||||
|
{
|
||||||
|
...mockCases[0],
|
||||||
|
attributes: { ...mockCases[0].attributes, settings: { syncAlerts: false } },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
caseCommentSavedObject: [{ ...mockCaseComments[3] }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
|
caseClient.client.updateAlertsStatus = jest.fn();
|
||||||
|
|
||||||
|
await caseClient.client.update({
|
||||||
|
caseClient: caseClient.client,
|
||||||
|
cases: patchCases,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(caseClient.client.updateAlertsStatus).toHaveBeenCalledWith({
|
||||||
|
ids: ['test-id'],
|
||||||
|
status: 'open',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it does NOT updates alert status when syncAlerts is turned off', async () => {
|
||||||
|
const patchCases = {
|
||||||
|
cases: [
|
||||||
|
{
|
||||||
|
id: 'mock-id-1',
|
||||||
|
settings: { syncAlerts: false },
|
||||||
|
version: 'WzAsMV0=',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||||
|
caseSavedObject: mockCases,
|
||||||
|
caseCommentSavedObject: [{ ...mockCaseComments[3] }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
|
caseClient.client.updateAlertsStatus = jest.fn();
|
||||||
|
|
||||||
|
await caseClient.client.update({
|
||||||
|
caseClient: caseClient.client,
|
||||||
|
cases: patchCases,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(caseClient.client.updateAlertsStatus).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it updates alert status for multiple cases', async () => {
|
||||||
|
const patchCases = {
|
||||||
|
cases: [
|
||||||
|
{
|
||||||
|
id: 'mock-id-1',
|
||||||
|
settings: { syncAlerts: true },
|
||||||
|
version: 'WzAsMV0=',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mock-id-2',
|
||||||
|
status: CaseStatuses.closed,
|
||||||
|
version: 'WzQsMV0=',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||||
|
caseSavedObject: [
|
||||||
|
{
|
||||||
|
...mockCases[0],
|
||||||
|
attributes: { ...mockCases[0].attributes, settings: { syncAlerts: false } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...mockCases[1],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
caseCommentSavedObject: [{ ...mockCaseComments[3] }, { ...mockCaseComments[4] }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
|
caseClient.client.updateAlertsStatus = jest.fn();
|
||||||
|
|
||||||
|
await caseClient.client.update({
|
||||||
|
caseClient: caseClient.client,
|
||||||
|
cases: patchCases,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(caseClient.client.updateAlertsStatus).toHaveBeenNthCalledWith(1, {
|
||||||
|
ids: ['test-id', 'test-id-2'],
|
||||||
|
status: 'open',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(caseClient.client.updateAlertsStatus).toHaveBeenNthCalledWith(2, {
|
||||||
|
ids: ['test-id', 'test-id-2'],
|
||||||
|
status: 'closed',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it does NOT call updateAlertsStatus when there is no comments of type alerts', async () => {
|
||||||
|
const patchCases = {
|
||||||
|
cases: [
|
||||||
|
{
|
||||||
|
id: 'mock-id-1',
|
||||||
|
status: CaseStatuses.closed,
|
||||||
|
version: 'WzAsMV0=',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||||
|
caseSavedObject: mockCases,
|
||||||
|
});
|
||||||
|
|
||||||
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
|
caseClient.client.updateAlertsStatus = jest.fn();
|
||||||
|
|
||||||
|
await caseClient.client.update({
|
||||||
|
caseClient: caseClient.client,
|
||||||
|
cases: patchCases,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(caseClient.client.updateAlertsStatus).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('unhappy path', () => {
|
describe('unhappy path', () => {
|
||||||
|
@ -293,7 +547,7 @@ describe('update', () => {
|
||||||
caseSavedObject: mockCases,
|
caseSavedObject: mockCases,
|
||||||
});
|
});
|
||||||
|
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
caseClient.client
|
caseClient.client
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
.update({ cases: patchCases })
|
.update({ cases: patchCases })
|
||||||
|
@ -324,7 +578,7 @@ describe('update', () => {
|
||||||
caseSavedObject: mockCases,
|
caseSavedObject: mockCases,
|
||||||
});
|
});
|
||||||
|
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
caseClient.client
|
caseClient.client
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
.update({ cases: patchCases })
|
.update({ cases: patchCases })
|
||||||
|
@ -351,7 +605,7 @@ describe('update', () => {
|
||||||
caseSavedObject: mockCases,
|
caseSavedObject: mockCases,
|
||||||
});
|
});
|
||||||
|
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
caseClient.client.update({ caseClient: caseClient.client, cases: patchCases }).catch((e) => {
|
caseClient.client.update({ caseClient: caseClient.client, cases: patchCases }).catch((e) => {
|
||||||
expect(e).not.toBeNull();
|
expect(e).not.toBeNull();
|
||||||
expect(e.isBoom).toBe(true);
|
expect(e.isBoom).toBe(true);
|
||||||
|
@ -381,7 +635,7 @@ describe('update', () => {
|
||||||
caseSavedObject: mockCases,
|
caseSavedObject: mockCases,
|
||||||
});
|
});
|
||||||
|
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
caseClient.client.update({ caseClient: caseClient.client, cases: patchCases }).catch((e) => {
|
caseClient.client.update({ caseClient: caseClient.client, cases: patchCases }).catch((e) => {
|
||||||
expect(e).not.toBeNull();
|
expect(e).not.toBeNull();
|
||||||
expect(e.isBoom).toBe(true);
|
expect(e.isBoom).toBe(true);
|
||||||
|
@ -408,7 +662,7 @@ describe('update', () => {
|
||||||
caseSavedObject: mockCases,
|
caseSavedObject: mockCases,
|
||||||
});
|
});
|
||||||
|
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
caseClient.client.update({ caseClient: caseClient.client, cases: patchCases }).catch((e) => {
|
caseClient.client.update({ caseClient: caseClient.client, cases: patchCases }).catch((e) => {
|
||||||
expect(e).not.toBeNull();
|
expect(e).not.toBeNull();
|
||||||
expect(e.isBoom).toBe(true);
|
expect(e.isBoom).toBe(true);
|
||||||
|
|
|
@ -110,7 +110,8 @@ export const update = ({
|
||||||
};
|
};
|
||||||
} else if (
|
} else if (
|
||||||
updateCaseAttributes.status &&
|
updateCaseAttributes.status &&
|
||||||
updateCaseAttributes.status === CaseStatuses.open
|
(updateCaseAttributes.status === CaseStatuses.open ||
|
||||||
|
updateCaseAttributes.status === CaseStatuses['in-progress'])
|
||||||
) {
|
) {
|
||||||
closedInfo = {
|
closedInfo = {
|
||||||
closed_at: null,
|
closed_at: null,
|
||||||
|
@ -182,11 +183,14 @@ export const update = ({
|
||||||
// The filter guarantees that the comments will be of type alert
|
// The filter guarantees that the comments will be of type alert
|
||||||
})) as SavedObjectsFindResponse<{ alertId: string }>;
|
})) as SavedObjectsFindResponse<{ alertId: string }>;
|
||||||
|
|
||||||
caseClient.updateAlertsStatus({
|
const commentIds = caseComments.saved_objects.map(({ attributes: { alertId } }) => alertId);
|
||||||
ids: caseComments.saved_objects.map(({ attributes: { alertId } }) => alertId),
|
if (commentIds.length > 0) {
|
||||||
// Either there is a status update or the syncAlerts got turned on.
|
caseClient.updateAlertsStatus({
|
||||||
status: theCase.status ?? currentCase?.attributes.status ?? CaseStatuses.open,
|
ids: commentIds,
|
||||||
});
|
// Either there is a status update or the syncAlerts got turned on.
|
||||||
|
status: theCase.status ?? currentCase?.attributes.status ?? CaseStatuses.open,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const returnUpdatedCase = myCases.saved_objects
|
const returnUpdatedCase = myCases.saved_objects
|
||||||
|
|
|
@ -29,7 +29,7 @@ describe('addComment', () => {
|
||||||
caseCommentSavedObject: mockCaseComments,
|
caseCommentSavedObject: mockCaseComments,
|
||||||
});
|
});
|
||||||
|
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
const res = await caseClient.client.addComment({
|
const res = await caseClient.client.addComment({
|
||||||
caseClient: caseClient.client,
|
caseClient: caseClient.client,
|
||||||
caseId: 'mock-id-1',
|
caseId: 'mock-id-1',
|
||||||
|
@ -65,7 +65,7 @@ describe('addComment', () => {
|
||||||
caseCommentSavedObject: mockCaseComments,
|
caseCommentSavedObject: mockCaseComments,
|
||||||
});
|
});
|
||||||
|
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
const res = await caseClient.client.addComment({
|
const res = await caseClient.client.addComment({
|
||||||
caseClient: caseClient.client,
|
caseClient: caseClient.client,
|
||||||
caseId: 'mock-id-1',
|
caseId: 'mock-id-1',
|
||||||
|
@ -103,7 +103,7 @@ describe('addComment', () => {
|
||||||
caseCommentSavedObject: mockCaseComments,
|
caseCommentSavedObject: mockCaseComments,
|
||||||
});
|
});
|
||||||
|
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
const res = await caseClient.client.addComment({
|
const res = await caseClient.client.addComment({
|
||||||
caseClient: caseClient.client,
|
caseClient: caseClient.client,
|
||||||
caseId: 'mock-id-1',
|
caseId: 'mock-id-1',
|
||||||
|
@ -127,7 +127,7 @@ describe('addComment', () => {
|
||||||
caseCommentSavedObject: mockCaseComments,
|
caseCommentSavedObject: mockCaseComments,
|
||||||
});
|
});
|
||||||
|
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
await caseClient.client.addComment({
|
await caseClient.client.addComment({
|
||||||
caseClient: caseClient.client,
|
caseClient: caseClient.client,
|
||||||
caseId: 'mock-id-1',
|
caseId: 'mock-id-1',
|
||||||
|
@ -175,7 +175,10 @@ describe('addComment', () => {
|
||||||
caseCommentSavedObject: mockCaseComments,
|
caseCommentSavedObject: mockCaseComments,
|
||||||
});
|
});
|
||||||
|
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient, true);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({
|
||||||
|
savedObjectsClient,
|
||||||
|
badAuth: true,
|
||||||
|
});
|
||||||
const res = await caseClient.client.addComment({
|
const res = await caseClient.client.addComment({
|
||||||
caseClient: caseClient.client,
|
caseClient: caseClient.client,
|
||||||
caseId: 'mock-id-1',
|
caseId: 'mock-id-1',
|
||||||
|
@ -203,6 +206,66 @@ describe('addComment', () => {
|
||||||
version: 'WzksMV0=',
|
version: 'WzksMV0=',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('it update the status of the alert if the case is synced with alerts', async () => {
|
||||||
|
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||||
|
caseSavedObject: mockCases,
|
||||||
|
caseCommentSavedObject: mockCaseComments,
|
||||||
|
});
|
||||||
|
|
||||||
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({
|
||||||
|
savedObjectsClient,
|
||||||
|
badAuth: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
caseClient.client.updateAlertsStatus = jest.fn();
|
||||||
|
|
||||||
|
await caseClient.client.addComment({
|
||||||
|
caseClient: caseClient.client,
|
||||||
|
caseId: 'mock-id-1',
|
||||||
|
comment: {
|
||||||
|
type: CommentType.alert,
|
||||||
|
alertId: 'test-alert',
|
||||||
|
index: 'test-index',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(caseClient.client.updateAlertsStatus).toHaveBeenCalledWith({
|
||||||
|
ids: ['test-alert'],
|
||||||
|
status: 'open',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it should NOT update the status of the alert if the case is NOT synced with alerts', async () => {
|
||||||
|
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||||
|
caseSavedObject: [
|
||||||
|
{
|
||||||
|
...mockCases[0],
|
||||||
|
attributes: { ...mockCases[0].attributes, settings: { syncAlerts: false } },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
caseCommentSavedObject: mockCaseComments,
|
||||||
|
});
|
||||||
|
|
||||||
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({
|
||||||
|
savedObjectsClient,
|
||||||
|
badAuth: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
caseClient.client.updateAlertsStatus = jest.fn();
|
||||||
|
|
||||||
|
await caseClient.client.addComment({
|
||||||
|
caseClient: caseClient.client,
|
||||||
|
caseId: 'mock-id-1',
|
||||||
|
comment: {
|
||||||
|
type: CommentType.alert,
|
||||||
|
alertId: 'test-alert',
|
||||||
|
index: 'test-index',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(caseClient.client.updateAlertsStatus).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('unhappy path', () => {
|
describe('unhappy path', () => {
|
||||||
|
@ -213,7 +276,7 @@ describe('addComment', () => {
|
||||||
caseSavedObject: mockCases,
|
caseSavedObject: mockCases,
|
||||||
caseCommentSavedObject: mockCaseComments,
|
caseCommentSavedObject: mockCaseComments,
|
||||||
});
|
});
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
caseClient.client
|
caseClient.client
|
||||||
.addComment({
|
.addComment({
|
||||||
caseId: 'mock-id-1',
|
caseId: 'mock-id-1',
|
||||||
|
@ -235,7 +298,7 @@ describe('addComment', () => {
|
||||||
caseCommentSavedObject: mockCaseComments,
|
caseCommentSavedObject: mockCaseComments,
|
||||||
});
|
});
|
||||||
|
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
const allRequestAttributes = {
|
const allRequestAttributes = {
|
||||||
type: CommentType.user,
|
type: CommentType.user,
|
||||||
comment: 'a comment',
|
comment: 'a comment',
|
||||||
|
@ -267,7 +330,7 @@ describe('addComment', () => {
|
||||||
caseCommentSavedObject: mockCaseComments,
|
caseCommentSavedObject: mockCaseComments,
|
||||||
});
|
});
|
||||||
|
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
|
|
||||||
['alertId', 'index'].forEach((attribute) => {
|
['alertId', 'index'].forEach((attribute) => {
|
||||||
caseClient.client
|
caseClient.client
|
||||||
|
@ -296,7 +359,7 @@ describe('addComment', () => {
|
||||||
caseCommentSavedObject: mockCaseComments,
|
caseCommentSavedObject: mockCaseComments,
|
||||||
});
|
});
|
||||||
|
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
const allRequestAttributes = {
|
const allRequestAttributes = {
|
||||||
type: CommentType.alert,
|
type: CommentType.alert,
|
||||||
index: 'test-index',
|
index: 'test-index',
|
||||||
|
@ -329,7 +392,7 @@ describe('addComment', () => {
|
||||||
caseCommentSavedObject: mockCaseComments,
|
caseCommentSavedObject: mockCaseComments,
|
||||||
});
|
});
|
||||||
|
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
|
|
||||||
['comment'].forEach((attribute) => {
|
['comment'].forEach((attribute) => {
|
||||||
caseClient.client
|
caseClient.client
|
||||||
|
@ -358,7 +421,7 @@ describe('addComment', () => {
|
||||||
caseSavedObject: mockCases,
|
caseSavedObject: mockCases,
|
||||||
caseCommentSavedObject: mockCaseComments,
|
caseCommentSavedObject: mockCaseComments,
|
||||||
});
|
});
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
caseClient.client
|
caseClient.client
|
||||||
.addComment({
|
.addComment({
|
||||||
caseClient: caseClient.client,
|
caseClient: caseClient.client,
|
||||||
|
@ -382,7 +445,7 @@ describe('addComment', () => {
|
||||||
caseSavedObject: mockCases,
|
caseSavedObject: mockCases,
|
||||||
caseCommentSavedObject: mockCaseComments,
|
caseCommentSavedObject: mockCaseComments,
|
||||||
});
|
});
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
caseClient.client
|
caseClient.client
|
||||||
.addComment({
|
.addComment({
|
||||||
caseClient: caseClient.client,
|
caseClient: caseClient.client,
|
||||||
|
@ -398,5 +461,31 @@ describe('addComment', () => {
|
||||||
expect(e.output.statusCode).toBe(400);
|
expect(e.output.statusCode).toBe(400);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('it throws when the case is closed and the comment is of type alert', async () => {
|
||||||
|
expect.assertions(3);
|
||||||
|
|
||||||
|
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||||
|
caseSavedObject: mockCases,
|
||||||
|
caseCommentSavedObject: mockCaseComments,
|
||||||
|
});
|
||||||
|
|
||||||
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
|
caseClient.client
|
||||||
|
.addComment({
|
||||||
|
caseClient: caseClient.client,
|
||||||
|
caseId: 'mock-id-4',
|
||||||
|
comment: {
|
||||||
|
type: CommentType.alert,
|
||||||
|
alertId: 'test-alert',
|
||||||
|
index: 'test-index',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
expect(e).not.toBeNull();
|
||||||
|
expect(e.isBoom).toBe(true);
|
||||||
|
expect(e.output.statusCode).toBe(400);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -22,7 +22,7 @@ describe('get_fields', () => {
|
||||||
const savedObjectsClient = createMockSavedObjectsRepository({
|
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||||
caseMappingsSavedObject: mockCaseMappings,
|
caseMappingsSavedObject: mockCaseMappings,
|
||||||
});
|
});
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
const res = await caseClient.client.getFields({
|
const res = await caseClient.client.getFields({
|
||||||
actionsClient: actionsMock,
|
actionsClient: actionsMock,
|
||||||
connectorType: ConnectorTypes.jira,
|
connectorType: ConnectorTypes.jira,
|
||||||
|
@ -43,7 +43,7 @@ describe('get_fields', () => {
|
||||||
const savedObjectsClient = createMockSavedObjectsRepository({
|
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||||
caseMappingsSavedObject: mockCaseMappings,
|
caseMappingsSavedObject: mockCaseMappings,
|
||||||
});
|
});
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
await caseClient.client
|
await caseClient.client
|
||||||
.getFields({
|
.getFields({
|
||||||
actionsClient: { ...actionsMock, execute: jest.fn().mockReturnValue(actionsErrResponse) },
|
actionsClient: { ...actionsMock, execute: jest.fn().mockReturnValue(actionsErrResponse) },
|
||||||
|
|
|
@ -27,7 +27,7 @@ describe('get_mappings', () => {
|
||||||
const savedObjectsClient = createMockSavedObjectsRepository({
|
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||||
caseMappingsSavedObject: mockCaseMappings,
|
caseMappingsSavedObject: mockCaseMappings,
|
||||||
});
|
});
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
const res = await caseClient.client.getMappings({
|
const res = await caseClient.client.getMappings({
|
||||||
actionsClient: actionsMock,
|
actionsClient: actionsMock,
|
||||||
caseClient: caseClient.client,
|
caseClient: caseClient.client,
|
||||||
|
@ -41,7 +41,7 @@ describe('get_mappings', () => {
|
||||||
const savedObjectsClient = createMockSavedObjectsRepository({
|
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||||
caseMappingsSavedObject: [],
|
caseMappingsSavedObject: [],
|
||||||
});
|
});
|
||||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||||
const res = await caseClient.client.getMappings({
|
const res = await caseClient.client.getMappings({
|
||||||
actionsClient: actionsMock,
|
actionsClient: actionsMock,
|
||||||
caseClient: caseClient.client,
|
caseClient: caseClient.client,
|
||||||
|
|
|
@ -4,11 +4,12 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { omit } from 'lodash/fp';
|
||||||
import { KibanaRequest, RequestHandlerContext } from 'kibana/server';
|
import { KibanaRequest, RequestHandlerContext } from 'kibana/server';
|
||||||
import { loggingSystemMock, elasticsearchServiceMock } from '../../../../../src/core/server/mocks';
|
import { loggingSystemMock } from '../../../../../src/core/server/mocks';
|
||||||
import { actionsClientMock } from '../../../actions/server/mocks';
|
import { actionsClientMock } from '../../../actions/server/mocks';
|
||||||
import {
|
import {
|
||||||
AlertService,
|
AlertServiceContract,
|
||||||
CaseConfigureService,
|
CaseConfigureService,
|
||||||
CaseService,
|
CaseService,
|
||||||
CaseUserActionServiceSetup,
|
CaseUserActionServiceSetup,
|
||||||
|
@ -29,17 +30,24 @@ export const createCaseClientMock = (): CaseClientMock => ({
|
||||||
updateAlertsStatus: jest.fn(),
|
updateAlertsStatus: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const createCaseClientWithMockSavedObjectsClient = async (
|
export const createCaseClientWithMockSavedObjectsClient = async ({
|
||||||
savedObjectsClient: any,
|
savedObjectsClient,
|
||||||
badAuth: boolean = false
|
badAuth = false,
|
||||||
): Promise<{
|
omitFromContext = [],
|
||||||
|
}: {
|
||||||
|
savedObjectsClient: any;
|
||||||
|
badAuth?: boolean;
|
||||||
|
omitFromContext?: string[];
|
||||||
|
}): Promise<{
|
||||||
client: CaseClient;
|
client: CaseClient;
|
||||||
services: { userActionService: jest.Mocked<CaseUserActionServiceSetup> };
|
services: {
|
||||||
|
userActionService: jest.Mocked<CaseUserActionServiceSetup>;
|
||||||
|
alertsService: jest.Mocked<AlertServiceContract>;
|
||||||
|
};
|
||||||
}> => {
|
}> => {
|
||||||
const actionsMock = actionsClientMock.create();
|
const actionsMock = actionsClientMock.create();
|
||||||
actionsMock.getAll.mockImplementation(() => Promise.resolve(getActions()));
|
actionsMock.getAll.mockImplementation(() => Promise.resolve(getActions()));
|
||||||
const log = loggingSystemMock.create().get('case');
|
const log = loggingSystemMock.create().get('case');
|
||||||
const esClientMock = elasticsearchServiceMock.createClusterClient();
|
|
||||||
const request = {} as KibanaRequest;
|
const request = {} as KibanaRequest;
|
||||||
|
|
||||||
const caseServicePlugin = new CaseService(log);
|
const caseServicePlugin = new CaseService(log);
|
||||||
|
@ -56,10 +64,10 @@ export const createCaseClientWithMockSavedObjectsClient = async (
|
||||||
postUserActions: jest.fn(),
|
postUserActions: jest.fn(),
|
||||||
getUserActions: jest.fn(),
|
getUserActions: jest.fn(),
|
||||||
};
|
};
|
||||||
const alertsService = new AlertService();
|
|
||||||
alertsService.initialize(esClientMock);
|
|
||||||
|
|
||||||
const context = ({
|
const alertsService = { initialize: jest.fn(), updateAlertsStatus: jest.fn() };
|
||||||
|
|
||||||
|
const context = {
|
||||||
core: {
|
core: {
|
||||||
savedObjects: {
|
savedObjects: {
|
||||||
client: savedObjectsClient,
|
client: savedObjectsClient,
|
||||||
|
@ -74,7 +82,7 @@ export const createCaseClientWithMockSavedObjectsClient = async (
|
||||||
getSignalsIndex: () => '.siem-signals',
|
getSignalsIndex: () => '.siem-signals',
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
} as unknown) as RequestHandlerContext;
|
};
|
||||||
|
|
||||||
const caseClient = createCaseClient({
|
const caseClient = createCaseClient({
|
||||||
savedObjectsClient,
|
savedObjectsClient,
|
||||||
|
@ -84,10 +92,10 @@ export const createCaseClientWithMockSavedObjectsClient = async (
|
||||||
connectorMappingsService,
|
connectorMappingsService,
|
||||||
userActionService,
|
userActionService,
|
||||||
alertsService,
|
alertsService,
|
||||||
context,
|
context: (omit(omitFromContext, context) as unknown) as RequestHandlerContext,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
client: caseClient,
|
client: caseClient,
|
||||||
services: { userActionService },
|
services: { userActionService, alertsService },
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -28,7 +28,7 @@ export const createMockSavedObjectsRepository = ({
|
||||||
caseCommentSavedObject?: any[];
|
caseCommentSavedObject?: any[];
|
||||||
caseConfigureSavedObject?: any[];
|
caseConfigureSavedObject?: any[];
|
||||||
caseMappingsSavedObject?: any[];
|
caseMappingsSavedObject?: any[];
|
||||||
}) => {
|
} = {}) => {
|
||||||
const mockSavedObjectsClientContract = ({
|
const mockSavedObjectsClientContract = ({
|
||||||
bulkGet: jest.fn((objects: SavedObjectsBulkGetObject[]) => {
|
bulkGet: jest.fn((objects: SavedObjectsBulkGetObject[]) => {
|
||||||
return {
|
return {
|
||||||
|
@ -100,9 +100,12 @@ export const createMockSavedObjectsRepository = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
findArgs.type === CASE_CONFIGURE_SAVED_OBJECT &&
|
(findArgs.type === CASE_CONFIGURE_SAVED_OBJECT &&
|
||||||
caseConfigureSavedObject[0] &&
|
caseConfigureSavedObject[0] &&
|
||||||
caseConfigureSavedObject[0].id === 'throw-error-find'
|
caseConfigureSavedObject[0].id === 'throw-error-find') ||
|
||||||
|
(findArgs.type === CASE_SAVED_OBJECT &&
|
||||||
|
caseSavedObject[0] &&
|
||||||
|
caseSavedObject[0].id === 'throw-error-find')
|
||||||
) {
|
) {
|
||||||
throw SavedObjectsErrorHelpers.createGenericNotFoundError('Error thrown for testing');
|
throw SavedObjectsErrorHelpers.createGenericNotFoundError('Error thrown for testing');
|
||||||
}
|
}
|
||||||
|
|
|
@ -348,6 +348,38 @@ export const mockCaseComments: Array<SavedObject<CommentAttributes>> = [
|
||||||
updated_at: '2019-11-25T22:32:30.608Z',
|
updated_at: '2019-11-25T22:32:30.608Z',
|
||||||
version: 'WzYsMV0=',
|
version: 'WzYsMV0=',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'cases-comment',
|
||||||
|
id: 'mock-comment-5',
|
||||||
|
attributes: {
|
||||||
|
type: CommentType.alert,
|
||||||
|
index: 'test-index-2',
|
||||||
|
alertId: 'test-id-2',
|
||||||
|
created_at: '2019-11-25T22:32:30.608Z',
|
||||||
|
created_by: {
|
||||||
|
full_name: 'elastic',
|
||||||
|
email: 'testemail@elastic.co',
|
||||||
|
username: 'elastic',
|
||||||
|
},
|
||||||
|
pushed_at: null,
|
||||||
|
pushed_by: null,
|
||||||
|
updated_at: '2019-11-25T22:32:30.608Z',
|
||||||
|
updated_by: {
|
||||||
|
full_name: 'elastic',
|
||||||
|
email: 'testemail@elastic.co',
|
||||||
|
username: 'elastic',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
type: 'cases',
|
||||||
|
name: 'associated-cases',
|
||||||
|
id: 'mock-id-4',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
updated_at: '2019-11-25T22:32:30.608Z',
|
||||||
|
version: 'WzYsMV0=',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const mockCaseConfigure: Array<SavedObject<ESCasesConfigureAttributes>> = [
|
export const mockCaseConfigure: Array<SavedObject<ESCasesConfigureAttributes>> = [
|
||||||
|
|
|
@ -104,7 +104,7 @@ describe('GET case', () => {
|
||||||
const response = await routeHandler(theContext, request, kibanaResponseFactory);
|
const response = await routeHandler(theContext, request, kibanaResponseFactory);
|
||||||
|
|
||||||
expect(response.status).toEqual(200);
|
expect(response.status).toEqual(200);
|
||||||
expect(response.payload.comments).toHaveLength(4);
|
expect(response.payload.comments).toHaveLength(5);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`returns an error when thrown from getAllCaseComments`, async () => {
|
it(`returns an error when thrown from getAllCaseComments`, async () => {
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* 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 { kibanaResponseFactory, RequestHandler } from 'src/core/server';
|
||||||
|
import { httpServerMock } from 'src/core/server/mocks';
|
||||||
|
|
||||||
|
import {
|
||||||
|
createMockSavedObjectsRepository,
|
||||||
|
createRoute,
|
||||||
|
createRouteContext,
|
||||||
|
mockCases,
|
||||||
|
} from '../../__fixtures__';
|
||||||
|
import { initGetCasesStatusApi } from './get_status';
|
||||||
|
import { CASE_STATUS_URL } from '../../../../../common/constants';
|
||||||
|
|
||||||
|
describe('GET status', () => {
|
||||||
|
let routeHandler: RequestHandler<any, any, any>;
|
||||||
|
const findArgs = {
|
||||||
|
fields: [],
|
||||||
|
page: 1,
|
||||||
|
perPage: 1,
|
||||||
|
type: 'cases',
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
routeHandler = await createRoute(initGetCasesStatusApi, 'get');
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`returns the status`, async () => {
|
||||||
|
const request = httpServerMock.createKibanaRequest({
|
||||||
|
path: CASE_STATUS_URL,
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
|
||||||
|
const theContext = await createRouteContext(
|
||||||
|
createMockSavedObjectsRepository({
|
||||||
|
caseSavedObject: mockCases,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await routeHandler(theContext, request, kibanaResponseFactory);
|
||||||
|
expect(theContext.core.savedObjects.client.find).toHaveBeenNthCalledWith(1, {
|
||||||
|
...findArgs,
|
||||||
|
filter: 'cases.attributes.status: open',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(theContext.core.savedObjects.client.find).toHaveBeenNthCalledWith(2, {
|
||||||
|
...findArgs,
|
||||||
|
filter: 'cases.attributes.status: in-progress',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(theContext.core.savedObjects.client.find).toHaveBeenNthCalledWith(3, {
|
||||||
|
...findArgs,
|
||||||
|
filter: 'cases.attributes.status: closed',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.payload).toEqual({
|
||||||
|
count_open_cases: 4,
|
||||||
|
count_in_progress_cases: 4,
|
||||||
|
count_closed_cases: 4,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`returns an error when findCases throws`, async () => {
|
||||||
|
const request = httpServerMock.createKibanaRequest({
|
||||||
|
path: CASE_STATUS_URL,
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
|
||||||
|
const theContext = await createRouteContext(
|
||||||
|
createMockSavedObjectsRepository({
|
||||||
|
caseSavedObject: [{ ...mockCases[0], id: 'throw-error-find' }],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await routeHandler(theContext, request, kibanaResponseFactory);
|
||||||
|
expect(response.status).toEqual(404);
|
||||||
|
});
|
||||||
|
});
|
57
x-pack/plugins/case/server/services/alerts/index.test.ts
Normal file
57
x-pack/plugins/case/server/services/alerts/index.test.ts
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* 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 { KibanaRequest } from 'kibana/server';
|
||||||
|
import { elasticsearchServiceMock } from '../../../../../../src/core/server/mocks';
|
||||||
|
import { CaseStatuses } from '../../../common/api';
|
||||||
|
import { AlertService, AlertServiceContract } from '.';
|
||||||
|
|
||||||
|
describe('updateAlertsStatus', () => {
|
||||||
|
const esClientMock = elasticsearchServiceMock.createClusterClient();
|
||||||
|
|
||||||
|
describe('happy path', () => {
|
||||||
|
let alertService: AlertServiceContract;
|
||||||
|
const args = {
|
||||||
|
ids: ['alert-id-1'],
|
||||||
|
index: '.siem-signals',
|
||||||
|
request: {} as KibanaRequest,
|
||||||
|
status: CaseStatuses.closed,
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
alertService = new AlertService();
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it update the status of the alert correctly', async () => {
|
||||||
|
alertService.initialize(esClientMock);
|
||||||
|
await alertService.updateAlertsStatus(args);
|
||||||
|
|
||||||
|
expect(esClientMock.asScoped().asCurrentUser.updateByQuery).toHaveBeenCalledWith({
|
||||||
|
body: {
|
||||||
|
query: { ids: { values: args.ids } },
|
||||||
|
script: { lang: 'painless', source: `ctx._source.signal.status = '${args.status}'` },
|
||||||
|
},
|
||||||
|
conflicts: 'abort',
|
||||||
|
ignore_unavailable: true,
|
||||||
|
index: args.index,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('unhappy path', () => {
|
||||||
|
test('it throws when service is already initialized', async () => {
|
||||||
|
alertService.initialize(esClientMock);
|
||||||
|
expect(() => {
|
||||||
|
alertService.initialize(esClientMock);
|
||||||
|
}).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it throws when service is not initialized and try to update the status', async () => {
|
||||||
|
await expect(alertService.updateAlertsStatus(args)).rejects.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -7,14 +7,15 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
import { waitFor, act } from '@testing-library/react';
|
import { waitFor, act } from '@testing-library/react';
|
||||||
|
import { noop } from 'lodash/fp';
|
||||||
|
|
||||||
import { AddComment, AddCommentRefObject } from '.';
|
|
||||||
import { TestProviders } from '../../../common/mock';
|
import { TestProviders } from '../../../common/mock';
|
||||||
import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router';
|
import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router';
|
||||||
|
|
||||||
import { CommentRequest, CommentType } from '../../../../../case/common/api';
|
import { CommentRequest, CommentType } from '../../../../../case/common/api';
|
||||||
import { useInsertTimeline } from '../use_insert_timeline';
|
import { useInsertTimeline } from '../use_insert_timeline';
|
||||||
import { usePostComment } from '../../containers/use_post_comment';
|
import { usePostComment } from '../../containers/use_post_comment';
|
||||||
|
import { AddComment, AddCommentRefObject } from '.';
|
||||||
|
|
||||||
jest.mock('../../containers/use_post_comment');
|
jest.mock('../../containers/use_post_comment');
|
||||||
jest.mock('../use_insert_timeline');
|
jest.mock('../use_insert_timeline');
|
||||||
|
@ -34,7 +35,7 @@ const addCommentProps = {
|
||||||
showLoading: false,
|
showLoading: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultPostCommment = {
|
const defaultPostComment = {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
isError: false,
|
isError: false,
|
||||||
postComment,
|
postComment,
|
||||||
|
@ -48,7 +49,7 @@ const sampleData: CommentRequest = {
|
||||||
describe('AddComment ', () => {
|
describe('AddComment ', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.resetAllMocks();
|
jest.resetAllMocks();
|
||||||
usePostCommentMock.mockImplementation(() => defaultPostCommment);
|
usePostCommentMock.mockImplementation(() => defaultPostComment);
|
||||||
jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation);
|
jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -83,7 +84,7 @@ describe('AddComment ', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render spinner and disable submit when loading', () => {
|
it('should render spinner and disable submit when loading', () => {
|
||||||
usePostCommentMock.mockImplementation(() => ({ ...defaultPostCommment, isLoading: true }));
|
usePostCommentMock.mockImplementation(() => ({ ...defaultPostComment, isLoading: true }));
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<TestProviders>
|
<TestProviders>
|
||||||
<Router history={mockHistory}>
|
<Router history={mockHistory}>
|
||||||
|
@ -99,7 +100,7 @@ describe('AddComment ', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should disable submit button when disabled prop passed', () => {
|
it('should disable submit button when disabled prop passed', () => {
|
||||||
usePostCommentMock.mockImplementation(() => ({ ...defaultPostCommment, isLoading: true }));
|
usePostCommentMock.mockImplementation(() => ({ ...defaultPostComment, isLoading: true }));
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<TestProviders>
|
<TestProviders>
|
||||||
<Router history={mockHistory}>
|
<Router history={mockHistory}>
|
||||||
|
@ -141,8 +142,9 @@ describe('AddComment ', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('it should insert a timeline', async () => {
|
it('it should insert a timeline', async () => {
|
||||||
|
let attachTimeline = noop;
|
||||||
useInsertTimelineMock.mockImplementation((comment, onTimelineAttached) => {
|
useInsertTimelineMock.mockImplementation((comment, onTimelineAttached) => {
|
||||||
onTimelineAttached(`[title](url)`);
|
attachTimeline = onTimelineAttached;
|
||||||
});
|
});
|
||||||
|
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
|
@ -153,6 +155,10 @@ describe('AddComment ', () => {
|
||||||
</TestProviders>
|
</TestProviders>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
attachTimeline('[title](url)');
|
||||||
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(wrapper.find(`[data-test-subj="add-comment"] textarea`).text()).toBe('[title](url)');
|
expect(wrapper.find(`[data-test-subj="add-comment"] textarea`).text()).toBe('[title](url)');
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,9 +9,8 @@ import { mount } from 'enzyme';
|
||||||
import moment from 'moment-timezone';
|
import moment from 'moment-timezone';
|
||||||
import { waitFor } from '@testing-library/react';
|
import { waitFor } from '@testing-library/react';
|
||||||
import '../../../common/mock/match_media';
|
import '../../../common/mock/match_media';
|
||||||
import { AllCases } from '.';
|
|
||||||
import { TestProviders } from '../../../common/mock';
|
import { TestProviders } from '../../../common/mock';
|
||||||
import { useGetCasesMockState } from '../../containers/mock';
|
import { casesStatus, useGetCasesMockState } from '../../containers/mock';
|
||||||
import * as i18n from './translations';
|
import * as i18n from './translations';
|
||||||
|
|
||||||
import { CaseStatuses } from '../../../../../case/common/api';
|
import { CaseStatuses } from '../../../../../case/common/api';
|
||||||
|
@ -22,6 +21,7 @@ import { useGetCases } from '../../containers/use_get_cases';
|
||||||
import { useGetCasesStatus } from '../../containers/use_get_cases_status';
|
import { useGetCasesStatus } from '../../containers/use_get_cases_status';
|
||||||
import { useUpdateCases } from '../../containers/use_bulk_update_case';
|
import { useUpdateCases } from '../../containers/use_bulk_update_case';
|
||||||
import { getCasesColumns } from './columns';
|
import { getCasesColumns } from './columns';
|
||||||
|
import { AllCases } from '.';
|
||||||
|
|
||||||
jest.mock('../../containers/use_bulk_update_case');
|
jest.mock('../../containers/use_bulk_update_case');
|
||||||
jest.mock('../../containers/use_delete_cases');
|
jest.mock('../../containers/use_delete_cases');
|
||||||
|
@ -61,6 +61,7 @@ describe('AllCases', () => {
|
||||||
setQueryParams,
|
setQueryParams,
|
||||||
setSelectedCases,
|
setSelectedCases,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultDeleteCases = {
|
const defaultDeleteCases = {
|
||||||
dispatchResetIsDeleted,
|
dispatchResetIsDeleted,
|
||||||
handleOnDeleteConfirm,
|
handleOnDeleteConfirm,
|
||||||
|
@ -69,13 +70,14 @@ describe('AllCases', () => {
|
||||||
isDisplayConfirmDeleteModal: false,
|
isDisplayConfirmDeleteModal: false,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultCasesStatus = {
|
const defaultCasesStatus = {
|
||||||
countClosedCases: 0,
|
...casesStatus,
|
||||||
countOpenCases: 5,
|
|
||||||
fetchCasesStatus,
|
fetchCasesStatus,
|
||||||
isError: false,
|
isError: false,
|
||||||
isLoading: true,
|
isLoading: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultUpdateCases = {
|
const defaultUpdateCases = {
|
||||||
isUpdated: false,
|
isUpdated: false,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
@ -103,6 +105,7 @@ describe('AllCases', () => {
|
||||||
<AllCases userCanCrud={true} />
|
<AllCases userCanCrud={true} />
|
||||||
</TestProviders>
|
</TestProviders>
|
||||||
);
|
);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(wrapper.find(`a[data-test-subj="case-details-link"]`).first().prop('href')).toEqual(
|
expect(wrapper.find(`a[data-test-subj="case-details-link"]`).first().prop('href')).toEqual(
|
||||||
`/${useGetCasesMockState.data.cases[0].id}`
|
`/${useGetCasesMockState.data.cases[0].id}`
|
||||||
|
@ -128,6 +131,63 @@ describe('AllCases', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should render the stats', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<AllCases userCanCrud={true} />
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(wrapper.find('[data-test-subj="openStatsHeader"]').exists()).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
wrapper
|
||||||
|
.find('[data-test-subj="openStatsHeader"] .euiDescriptionList__description')
|
||||||
|
.first()
|
||||||
|
.text()
|
||||||
|
).toBe('20');
|
||||||
|
|
||||||
|
expect(wrapper.find('[data-test-subj="inProgressStatsHeader"]').exists()).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
wrapper
|
||||||
|
.find('[data-test-subj="inProgressStatsHeader"] .euiDescriptionList__description')
|
||||||
|
.first()
|
||||||
|
.text()
|
||||||
|
).toBe('40');
|
||||||
|
|
||||||
|
expect(wrapper.find('[data-test-subj="closedStatsHeader"]').exists()).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
wrapper
|
||||||
|
.find('[data-test-subj="closedStatsHeader"] .euiDescriptionList__description')
|
||||||
|
.first()
|
||||||
|
.text()
|
||||||
|
).toBe('130');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render the loading spinner when loading stats', async () => {
|
||||||
|
useGetCasesStatusMock.mockReturnValue({ ...defaultCasesStatus, isLoading: true });
|
||||||
|
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<AllCases userCanCrud={true} />
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(
|
||||||
|
wrapper.find('[data-test-subj="openStatsHeader-loading-spinner"]').exists()
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
wrapper.find('[data-test-subj="inProgressStatsHeader-loading-spinner"]').exists()
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
wrapper.find('[data-test-subj="closedStatsHeader-loading-spinner"]').exists()
|
||||||
|
).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should render empty fields', async () => {
|
it('should render empty fields', async () => {
|
||||||
useGetCasesMock.mockReturnValue({
|
useGetCasesMock.mockReturnValue({
|
||||||
...defaultGetCases,
|
...defaultGetCases,
|
||||||
|
@ -199,6 +259,7 @@ describe('AllCases', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('closes case when row action icon clicked', async () => {
|
it('closes case when row action icon clicked', async () => {
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<TestProviders>
|
<TestProviders>
|
||||||
|
@ -217,6 +278,7 @@ describe('AllCases', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('opens case when row action icon clicked', async () => {
|
it('opens case when row action icon clicked', async () => {
|
||||||
useGetCasesMock.mockReturnValue({
|
useGetCasesMock.mockReturnValue({
|
||||||
...defaultGetCases,
|
...defaultGetCases,
|
||||||
|
@ -240,6 +302,7 @@ describe('AllCases', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Bulk delete', async () => {
|
it('Bulk delete', async () => {
|
||||||
useGetCasesMock.mockReturnValue({
|
useGetCasesMock.mockReturnValue({
|
||||||
...defaultGetCases,
|
...defaultGetCases,
|
||||||
|
@ -277,6 +340,7 @@ describe('AllCases', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Bulk close status update', async () => {
|
it('Bulk close status update', async () => {
|
||||||
useGetCasesMock.mockReturnValue({
|
useGetCasesMock.mockReturnValue({
|
||||||
...defaultGetCases,
|
...defaultGetCases,
|
||||||
|
@ -294,6 +358,7 @@ describe('AllCases', () => {
|
||||||
expect(updateBulkStatus).toBeCalledWith(useGetCasesMockState.data.cases, CaseStatuses.closed);
|
expect(updateBulkStatus).toBeCalledWith(useGetCasesMockState.data.cases, CaseStatuses.closed);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Bulk open status update', async () => {
|
it('Bulk open status update', async () => {
|
||||||
useGetCasesMock.mockReturnValue({
|
useGetCasesMock.mockReturnValue({
|
||||||
...defaultGetCases,
|
...defaultGetCases,
|
||||||
|
@ -315,6 +380,7 @@ describe('AllCases', () => {
|
||||||
expect(updateBulkStatus).toBeCalledWith(useGetCasesMockState.data.cases, CaseStatuses.open);
|
expect(updateBulkStatus).toBeCalledWith(useGetCasesMockState.data.cases, CaseStatuses.open);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('isDeleted is true, refetch', async () => {
|
it('isDeleted is true, refetch', async () => {
|
||||||
useDeleteCasesMock.mockReturnValue({
|
useDeleteCasesMock.mockReturnValue({
|
||||||
...defaultDeleteCases,
|
...defaultDeleteCases,
|
||||||
|
@ -492,4 +558,73 @@ describe('AllCases', () => {
|
||||||
expect(onRowClick).not.toHaveBeenCalled();
|
expect(onRowClick).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should change the status to closed', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<AllCases userCanCrud={true} isModal={false} />
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
wrapper.find('button[data-test-subj="case-status-filter"]').simulate('click');
|
||||||
|
wrapper.find('button[data-test-subj="case-status-filter-closed"]').simulate('click');
|
||||||
|
expect(setQueryParams).toBeCalledWith({
|
||||||
|
sortField: 'closedAt',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should change the status to in-progress', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<AllCases userCanCrud={true} isModal={false} />
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
wrapper.find('button[data-test-subj="case-status-filter"]').simulate('click');
|
||||||
|
wrapper.find('button[data-test-subj="case-status-filter-in-progress"]').simulate('click');
|
||||||
|
expect(setQueryParams).toBeCalledWith({
|
||||||
|
sortField: 'updatedAt',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should change the status to open', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<AllCases userCanCrud={true} isModal={false} />
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
wrapper.find('button[data-test-subj="case-status-filter"]').simulate('click');
|
||||||
|
wrapper.find('button[data-test-subj="case-status-filter-open"]').simulate('click');
|
||||||
|
expect(setQueryParams).toBeCalledWith({
|
||||||
|
sortField: 'createdAt',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show the correct count on stats', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<AllCases userCanCrud={true} isModal={false} />
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
wrapper.find('button[data-test-subj="case-status-filter"]').simulate('click');
|
||||||
|
expect(wrapper.find('button[data-test-subj="case-status-filter-open"]').text()).toBe(
|
||||||
|
'Open (20)'
|
||||||
|
);
|
||||||
|
expect(wrapper.find('button[data-test-subj="case-status-filter-in-progress"]').text()).toBe(
|
||||||
|
'In progress (40)'
|
||||||
|
);
|
||||||
|
expect(wrapper.find('button[data-test-subj="case-status-filter-closed"]').text()).toBe(
|
||||||
|
'Closed (130)'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* 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 React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import { waitFor } from '@testing-library/react';
|
||||||
|
|
||||||
|
import { CaseStatuses } from '../../../../../case/common/api';
|
||||||
|
import { StatusFilter } from './status_filter';
|
||||||
|
|
||||||
|
const stats = {
|
||||||
|
[CaseStatuses.open]: 2,
|
||||||
|
[CaseStatuses['in-progress']]: 5,
|
||||||
|
[CaseStatuses.closed]: 7,
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('StatusFilter', () => {
|
||||||
|
const onStatusChanged = jest.fn();
|
||||||
|
const defaultProps = {
|
||||||
|
selectedStatus: CaseStatuses.open,
|
||||||
|
onStatusChanged,
|
||||||
|
stats,
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should render', () => {
|
||||||
|
const wrapper = mount(<StatusFilter {...defaultProps} />);
|
||||||
|
|
||||||
|
expect(wrapper.find('[data-test-subj="case-status-filter"]').exists()).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call onStatusChanged when changing status to open', async () => {
|
||||||
|
const wrapper = mount(<StatusFilter {...defaultProps} />);
|
||||||
|
|
||||||
|
wrapper.find('button[data-test-subj="case-status-filter"]').simulate('click');
|
||||||
|
wrapper.find('button[data-test-subj="case-status-filter-open"]').simulate('click');
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(onStatusChanged).toBeCalledWith('open');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call onStatusChanged when changing status to in-progress', async () => {
|
||||||
|
const wrapper = mount(<StatusFilter {...defaultProps} />);
|
||||||
|
|
||||||
|
wrapper.find('button[data-test-subj="case-status-filter"]').simulate('click');
|
||||||
|
wrapper.find('button[data-test-subj="case-status-filter-in-progress"]').simulate('click');
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(onStatusChanged).toBeCalledWith('in-progress');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call onStatusChanged when changing status to closed', async () => {
|
||||||
|
const wrapper = mount(<StatusFilter {...defaultProps} />);
|
||||||
|
|
||||||
|
wrapper.find('button[data-test-subj="case-status-filter"]').simulate('click');
|
||||||
|
wrapper.find('button[data-test-subj="case-status-filter-closed"]').simulate('click');
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(onStatusChanged).toBeCalledWith('closed');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -10,8 +10,8 @@ import { mount } from 'enzyme';
|
||||||
import { useDeleteCases } from '../../containers/use_delete_cases';
|
import { useDeleteCases } from '../../containers/use_delete_cases';
|
||||||
import { TestProviders } from '../../../common/mock';
|
import { TestProviders } from '../../../common/mock';
|
||||||
import { basicCase, basicPush } from '../../containers/mock';
|
import { basicCase, basicPush } from '../../containers/mock';
|
||||||
import { CaseViewActions } from './actions';
|
import { Actions } from './actions';
|
||||||
import * as i18n from './translations';
|
import * as i18n from '../case_view/translations';
|
||||||
jest.mock('../../containers/use_delete_cases');
|
jest.mock('../../containers/use_delete_cases');
|
||||||
const useDeleteCasesMock = useDeleteCases as jest.Mock;
|
const useDeleteCasesMock = useDeleteCases as jest.Mock;
|
||||||
|
|
||||||
|
@ -39,14 +39,16 @@ describe('CaseView actions', () => {
|
||||||
isDeleted: false,
|
isDeleted: false,
|
||||||
isDisplayConfirmDeleteModal: false,
|
isDisplayConfirmDeleteModal: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.resetAllMocks();
|
jest.resetAllMocks();
|
||||||
useDeleteCasesMock.mockImplementation(() => defaultDeleteState);
|
useDeleteCasesMock.mockImplementation(() => defaultDeleteState);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('clicking trash toggles modal', () => {
|
it('clicking trash toggles modal', () => {
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<TestProviders>
|
<TestProviders>
|
||||||
<CaseViewActions caseData={basicCase} currentExternalIncident={null} />
|
<Actions caseData={basicCase} currentExternalIncident={null} />
|
||||||
</TestProviders>
|
</TestProviders>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -56,6 +58,7 @@ describe('CaseView actions', () => {
|
||||||
wrapper.find('button[data-test-subj="property-actions-trash"]').simulate('click');
|
wrapper.find('button[data-test-subj="property-actions-trash"]').simulate('click');
|
||||||
expect(handleToggleModal).toHaveBeenCalled();
|
expect(handleToggleModal).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('toggle delete modal and confirm', () => {
|
it('toggle delete modal and confirm', () => {
|
||||||
useDeleteCasesMock.mockImplementation(() => ({
|
useDeleteCasesMock.mockImplementation(() => ({
|
||||||
...defaultDeleteState,
|
...defaultDeleteState,
|
||||||
|
@ -63,7 +66,7 @@ describe('CaseView actions', () => {
|
||||||
}));
|
}));
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<TestProviders>
|
<TestProviders>
|
||||||
<CaseViewActions caseData={basicCase} currentExternalIncident={null} />
|
<Actions caseData={basicCase} currentExternalIncident={null} />
|
||||||
</TestProviders>
|
</TestProviders>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -73,10 +76,11 @@ describe('CaseView actions', () => {
|
||||||
{ id: basicCase.id, title: basicCase.title },
|
{ id: basicCase.id, title: basicCase.title },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('displays active incident link', () => {
|
it('displays active incident link', () => {
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<TestProviders>
|
<TestProviders>
|
||||||
<CaseViewActions
|
<Actions
|
||||||
caseData={basicCase}
|
caseData={basicCase}
|
||||||
currentExternalIncident={{
|
currentExternalIncident={{
|
||||||
...basicPush,
|
...basicPush,
|
|
@ -7,7 +7,7 @@
|
||||||
import { isEmpty } from 'lodash/fp';
|
import { isEmpty } from 'lodash/fp';
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import * as i18n from './translations';
|
import * as i18n from '../case_view/translations';
|
||||||
import { useDeleteCases } from '../../containers/use_delete_cases';
|
import { useDeleteCases } from '../../containers/use_delete_cases';
|
||||||
import { ConfirmDeleteCaseModal } from '../confirm_delete_case';
|
import { ConfirmDeleteCaseModal } from '../confirm_delete_case';
|
||||||
import { PropertyActions } from '../property_actions';
|
import { PropertyActions } from '../property_actions';
|
||||||
|
@ -20,7 +20,7 @@ interface CaseViewActions {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CaseViewActionsComponent: React.FC<CaseViewActions> = ({
|
const ActionsComponent: React.FC<CaseViewActions> = ({
|
||||||
caseData,
|
caseData,
|
||||||
currentExternalIncident,
|
currentExternalIncident,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
|
@ -80,4 +80,4 @@ const CaseViewActionsComponent: React.FC<CaseViewActions> = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CaseViewActions = React.memo(CaseViewActionsComponent);
|
export const Actions = React.memo(ActionsComponent);
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* 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 { CaseStatuses } from '../../../../../case/common/api';
|
||||||
|
import { basicCase } from '../../containers/mock';
|
||||||
|
import { getStatusDate, getStatusTitle } from './helpers';
|
||||||
|
|
||||||
|
describe('helpers', () => {
|
||||||
|
const caseData = {
|
||||||
|
...basicCase,
|
||||||
|
status: CaseStatuses.open,
|
||||||
|
createdAt: 'createAt',
|
||||||
|
updatedAt: 'updatedAt',
|
||||||
|
closedAt: 'closedAt',
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('getStatusDate', () => {
|
||||||
|
it('it return the createdAt when the status is open', () => {
|
||||||
|
expect(getStatusDate(caseData)).toBe(caseData.createdAt);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it return the createdAt when the status is in-progress', () => {
|
||||||
|
expect(getStatusDate({ ...caseData, status: CaseStatuses['in-progress'] })).toBe(
|
||||||
|
caseData.updatedAt
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it return the createdAt when the status is closed', () => {
|
||||||
|
expect(getStatusDate({ ...caseData, status: CaseStatuses.closed })).toBe(caseData.closedAt);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getStatusTitle', () => {
|
||||||
|
it('it return the correct title for open status', () => {
|
||||||
|
expect(getStatusTitle(CaseStatuses.open)).toBe('Case opened');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it return the correct title for in-progress status', () => {
|
||||||
|
expect(getStatusTitle(CaseStatuses['in-progress'])).toBe('Case in progress');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it return the correct title for closed status', () => {
|
||||||
|
expect(getStatusTitle(CaseStatuses.closed)).toBe('Case closed');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,110 @@
|
||||||
|
/*
|
||||||
|
* 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 React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
|
||||||
|
import { basicCase } from '../../containers/mock';
|
||||||
|
import { CaseActionBar } from '.';
|
||||||
|
import { TestProviders } from '../../../common/mock';
|
||||||
|
|
||||||
|
describe('CaseActionBar', () => {
|
||||||
|
const onRefresh = jest.fn();
|
||||||
|
const onUpdateField = jest.fn();
|
||||||
|
const defaultProps = {
|
||||||
|
caseData: basicCase,
|
||||||
|
isLoading: false,
|
||||||
|
onRefresh,
|
||||||
|
onUpdateField,
|
||||||
|
currentExternalIncident: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it renders', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<CaseActionBar {...defaultProps} />
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find(`[data-test-subj="case-view-status"]`).exists()).toBeTruthy();
|
||||||
|
expect(wrapper.find(`[data-test-subj="case-action-bar-status-date"]`).exists()).toBeTruthy();
|
||||||
|
expect(wrapper.find(`[data-test-subj="case-view-status-dropdown"]`).exists()).toBeTruthy();
|
||||||
|
expect(wrapper.find(`[data-test-subj="sync-alerts-switch"]`).exists()).toBeTruthy();
|
||||||
|
expect(wrapper.find(`[data-test-subj="case-refresh"]`).exists()).toBeTruthy();
|
||||||
|
expect(wrapper.find(`[data-test-subj="case-view-actions"]`).exists()).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it should show correct status', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<CaseActionBar {...defaultProps} />
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find(`[data-test-subj="case-view-status-dropdown"]`).first().text()).toBe(
|
||||||
|
'Open'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it should show the correct date', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<CaseActionBar {...defaultProps} />
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find(`[data-test-subj="case-action-bar-status-date"]`).prop('value')).toBe(
|
||||||
|
basicCase.createdAt
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it call onRefresh', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<CaseActionBar {...defaultProps} />
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
wrapper.find(`[data-test-subj="case-refresh"]`).first().simulate('click');
|
||||||
|
expect(onRefresh).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it should call onUpdateField when changing status', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<CaseActionBar {...defaultProps} />
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
wrapper.find(`[data-test-subj="case-view-status-dropdown"] button`).simulate('click');
|
||||||
|
wrapper
|
||||||
|
.find(`[data-test-subj="case-view-status-dropdown-in-progress"] button`)
|
||||||
|
.simulate('click');
|
||||||
|
|
||||||
|
expect(onUpdateField).toHaveBeenCalledWith({ key: 'status', value: 'in-progress' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it should call onUpdateField when changing syncAlerts setting', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<CaseActionBar {...defaultProps} />
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
wrapper.find('button[data-test-subj="sync-alerts-switch"]').first().simulate('click');
|
||||||
|
|
||||||
|
expect(onUpdateField).toHaveBeenCalledWith({
|
||||||
|
key: 'settings',
|
||||||
|
value: {
|
||||||
|
syncAlerts: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -18,7 +18,7 @@ import {
|
||||||
import { CaseStatuses } from '../../../../../case/common/api';
|
import { CaseStatuses } from '../../../../../case/common/api';
|
||||||
import * as i18n from '../case_view/translations';
|
import * as i18n from '../case_view/translations';
|
||||||
import { FormattedRelativePreferenceDate } from '../../../common/components/formatted_date';
|
import { FormattedRelativePreferenceDate } from '../../../common/components/formatted_date';
|
||||||
import { CaseViewActions } from '../case_view/actions';
|
import { Actions } from './actions';
|
||||||
import { Case } from '../../containers/types';
|
import { Case } from '../../containers/types';
|
||||||
import { CaseService } from '../../containers/use_get_case_user_actions';
|
import { CaseService } from '../../containers/use_get_case_user_actions';
|
||||||
import { StatusContextMenu } from './status_context_menu';
|
import { StatusContextMenu } from './status_context_menu';
|
||||||
|
@ -124,8 +124,8 @@ const CaseActionBarComponent: React.FC<CaseActionBarProps> = ({
|
||||||
{i18n.CASE_REFRESH}
|
{i18n.CASE_REFRESH}
|
||||||
</EuiButtonEmpty>
|
</EuiButtonEmpty>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false} data-test-subj="case-view-actions">
|
||||||
<CaseViewActions
|
<Actions
|
||||||
caseData={caseData}
|
caseData={caseData}
|
||||||
currentExternalIncident={currentExternalIncident}
|
currentExternalIncident={currentExternalIncident}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* 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 React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
|
||||||
|
import { CaseStatuses } from '../../../../../case/common/api';
|
||||||
|
import { StatusContextMenu } from './status_context_menu';
|
||||||
|
|
||||||
|
describe('SyncAlertsSwitch', () => {
|
||||||
|
const onStatusChanged = jest.fn();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it renders', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<StatusContextMenu currentStatus={CaseStatuses.open} onStatusChanged={onStatusChanged} />
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find(`[data-test-subj="case-view-status-dropdown"]`).exists()).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it renders the current status correctly', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<StatusContextMenu currentStatus={CaseStatuses.closed} onStatusChanged={onStatusChanged} />
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find(`[data-test-subj="case-view-status-dropdown"]`).first().text()).toBe(
|
||||||
|
'Closed'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it changes the status', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<StatusContextMenu currentStatus={CaseStatuses.open} onStatusChanged={onStatusChanged} />
|
||||||
|
);
|
||||||
|
|
||||||
|
wrapper.find(`[data-test-subj="case-view-status-dropdown"] button`).simulate('click');
|
||||||
|
wrapper
|
||||||
|
.find(`[data-test-subj="case-view-status-dropdown-in-progress"] button`)
|
||||||
|
.simulate('click');
|
||||||
|
|
||||||
|
expect(onStatusChanged).toHaveBeenCalledWith('in-progress');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* 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 React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import { waitFor } from '@testing-library/react';
|
||||||
|
|
||||||
|
import { SyncAlertsSwitch } from './sync_alerts_switch';
|
||||||
|
|
||||||
|
describe('SyncAlertsSwitch', () => {
|
||||||
|
it('it renders', async () => {
|
||||||
|
const wrapper = mount(<SyncAlertsSwitch disabled={false} />);
|
||||||
|
|
||||||
|
expect(wrapper.find(`[data-test-subj="sync-alerts-switch"]`).exists()).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it toggles the switch', async () => {
|
||||||
|
const wrapper = mount(<SyncAlertsSwitch disabled={false} />);
|
||||||
|
|
||||||
|
wrapper.find('button[data-test-subj="sync-alerts-switch"]').first().simulate('click');
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(wrapper.find('[data-test-subj="sync-alerts-switch"]').first().prop('checked')).toBe(
|
||||||
|
false
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it disables the switch', async () => {
|
||||||
|
const wrapper = mount(<SyncAlertsSwitch disabled={true} />);
|
||||||
|
|
||||||
|
expect(wrapper.find(`[data-test-subj="sync-alerts-switch"]`).first().prop('disabled')).toBe(
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it start as off', async () => {
|
||||||
|
const wrapper = mount(<SyncAlertsSwitch disabled={false} isSynced={false} showLabel={true} />);
|
||||||
|
|
||||||
|
expect(wrapper.find(`[data-test-subj="sync-alerts-switch"]`).first().text()).toBe('Off');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it shows the correct labels', async () => {
|
||||||
|
const wrapper = mount(<SyncAlertsSwitch disabled={false} showLabel={true} />);
|
||||||
|
|
||||||
|
expect(wrapper.find('[data-test-subj="sync-alerts-switch"]').first().text()).toBe('On');
|
||||||
|
wrapper.find('button[data-test-subj="sync-alerts-switch"]').first().simulate('click');
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(wrapper.find(`[data-test-subj="sync-alerts-switch"]`).first().text()).toBe('Off');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -39,6 +39,7 @@ const SyncAlertsSwitchComponent: React.FC<Props> = ({
|
||||||
checked={isOn}
|
checked={isOn}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
data-test-subj="sync-alerts-switch"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* 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 { CommentType } from '../../../../../case/common/api';
|
||||||
|
import { Comment } from '../../containers/types';
|
||||||
|
|
||||||
|
import { getRuleIdsFromComments, buildAlertsQuery } from './helpers';
|
||||||
|
|
||||||
|
const comments: Comment[] = [
|
||||||
|
{
|
||||||
|
type: CommentType.alert,
|
||||||
|
alertId: 'alert-id-1',
|
||||||
|
index: 'alert-index-1',
|
||||||
|
id: 'comment-id',
|
||||||
|
createdAt: '2020-02-19T23:06:33.798Z',
|
||||||
|
createdBy: { username: 'elastic' },
|
||||||
|
pushedAt: null,
|
||||||
|
pushedBy: null,
|
||||||
|
updatedAt: null,
|
||||||
|
updatedBy: null,
|
||||||
|
version: 'WzQ3LDFc',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: CommentType.alert,
|
||||||
|
alertId: 'alert-id-2',
|
||||||
|
index: 'alert-index-2',
|
||||||
|
id: 'comment-id',
|
||||||
|
createdAt: '2020-02-19T23:06:33.798Z',
|
||||||
|
createdBy: { username: 'elastic' },
|
||||||
|
pushedAt: null,
|
||||||
|
pushedBy: null,
|
||||||
|
updatedAt: null,
|
||||||
|
updatedBy: null,
|
||||||
|
version: 'WzQ3LDFc',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('Case view helpers', () => {
|
||||||
|
describe('getRuleIdsFromComments', () => {
|
||||||
|
it('it returns the rules ids from the comments', () => {
|
||||||
|
expect(getRuleIdsFromComments(comments)).toEqual(['alert-id-1', 'alert-id-2']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('buildAlertsQuery', () => {
|
||||||
|
it('it builds the alerts query', () => {
|
||||||
|
expect(buildAlertsQuery(['alert-id-1', 'alert-id-2'])).toEqual({
|
||||||
|
query: {
|
||||||
|
bool: {
|
||||||
|
filter: {
|
||||||
|
bool: {
|
||||||
|
should: [{ match: { _id: 'alert-id-1' } }, { match: { _id: 'alert-id-2' } }],
|
||||||
|
minimum_should_match: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -10,7 +10,13 @@ import { mount } from 'enzyme';
|
||||||
import '../../../common/mock/match_media';
|
import '../../../common/mock/match_media';
|
||||||
import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router';
|
import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router';
|
||||||
import { CaseComponent, CaseProps, CaseView } from '.';
|
import { CaseComponent, CaseProps, CaseView } from '.';
|
||||||
import { basicCase, basicCaseClosed, caseUserActions } from '../../containers/mock';
|
import {
|
||||||
|
basicCase,
|
||||||
|
basicCaseClosed,
|
||||||
|
caseUserActions,
|
||||||
|
alertComment,
|
||||||
|
getAlertUserAction,
|
||||||
|
} from '../../containers/mock';
|
||||||
import { TestProviders } from '../../../common/mock';
|
import { TestProviders } from '../../../common/mock';
|
||||||
import { useUpdateCase } from '../../containers/use_update_case';
|
import { useUpdateCase } from '../../containers/use_update_case';
|
||||||
import { useGetCase } from '../../containers/use_get_case';
|
import { useGetCase } from '../../containers/use_get_case';
|
||||||
|
@ -51,6 +57,7 @@ export const caseProps: CaseProps = {
|
||||||
userCanCrud: true,
|
userCanCrud: true,
|
||||||
caseData: {
|
caseData: {
|
||||||
...basicCase,
|
...basicCase,
|
||||||
|
comments: [...basicCase.comments, alertComment],
|
||||||
connector: {
|
connector: {
|
||||||
id: 'resilient-2',
|
id: 'resilient-2',
|
||||||
name: 'Resilient',
|
name: 'Resilient',
|
||||||
|
@ -67,6 +74,33 @@ export const caseClosedProps: CaseProps = {
|
||||||
caseData: basicCaseClosed,
|
caseData: basicCaseClosed,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const alertsHit = [
|
||||||
|
{
|
||||||
|
_id: 'alert-id-1',
|
||||||
|
_index: 'alert-index-1',
|
||||||
|
_source: {
|
||||||
|
signal: {
|
||||||
|
rule: {
|
||||||
|
id: 'rule-id-1',
|
||||||
|
name: 'Awesome rule',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: 'alert-id-2',
|
||||||
|
_index: 'alert-index-2',
|
||||||
|
_source: {
|
||||||
|
signal: {
|
||||||
|
rule: {
|
||||||
|
id: 'rule-id-2',
|
||||||
|
name: 'Awesome rule 2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
describe('CaseView ', () => {
|
describe('CaseView ', () => {
|
||||||
const updateCaseProperty = jest.fn();
|
const updateCaseProperty = jest.fn();
|
||||||
const fetchCaseUserActions = jest.fn();
|
const fetchCaseUserActions = jest.fn();
|
||||||
|
@ -91,7 +125,7 @@ describe('CaseView ', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultUseGetCaseUserActions = {
|
const defaultUseGetCaseUserActions = {
|
||||||
caseUserActions,
|
caseUserActions: [...caseUserActions, getAlertUserAction()],
|
||||||
caseServices: {},
|
caseServices: {},
|
||||||
fetchCaseUserActions,
|
fetchCaseUserActions,
|
||||||
firstIndexPushToService: -1,
|
firstIndexPushToService: -1,
|
||||||
|
@ -103,6 +137,7 @@ describe('CaseView ', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
jest.resetAllMocks();
|
jest.resetAllMocks();
|
||||||
useUpdateCaseMock.mockImplementation(() => defaultUpdateCaseState);
|
useUpdateCaseMock.mockImplementation(() => defaultUpdateCaseState);
|
||||||
|
|
||||||
|
@ -111,8 +146,8 @@ describe('CaseView ', () => {
|
||||||
usePostPushToServiceMock.mockImplementation(() => ({ isLoading: false, postPushToService }));
|
usePostPushToServiceMock.mockImplementation(() => ({ isLoading: false, postPushToService }));
|
||||||
useConnectorsMock.mockImplementation(() => ({ connectors: connectorsMock, isLoading: false }));
|
useConnectorsMock.mockImplementation(() => ({ connectors: connectorsMock, isLoading: false }));
|
||||||
useQueryAlertsMock.mockImplementation(() => ({
|
useQueryAlertsMock.mockImplementation(() => ({
|
||||||
isLoading: false,
|
loading: false,
|
||||||
alerts: { hits: { hists: [] } },
|
data: { hits: { hits: alertsHit } },
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -124,6 +159,7 @@ describe('CaseView ', () => {
|
||||||
</Router>
|
</Router>
|
||||||
</TestProviders>
|
</TestProviders>
|
||||||
);
|
);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(wrapper.find(`[data-test-subj="case-view-title"]`).first().prop('title')).toEqual(
|
expect(wrapper.find(`[data-test-subj="case-view-title"]`).first().prop('title')).toEqual(
|
||||||
data.title
|
data.title
|
||||||
|
@ -188,7 +224,7 @@ describe('CaseView ', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should dispatch update state when status is changed', async () => {
|
it('should update status', async () => {
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<TestProviders>
|
<TestProviders>
|
||||||
<Router history={mockHistory}>
|
<Router history={mockHistory}>
|
||||||
|
@ -204,7 +240,11 @@ describe('CaseView ', () => {
|
||||||
.find('button[data-test-subj="case-view-status-dropdown-closed"]')
|
.find('button[data-test-subj="case-view-status-dropdown-closed"]')
|
||||||
.first()
|
.first()
|
||||||
.simulate('click');
|
.simulate('click');
|
||||||
expect(updateCaseProperty).toHaveBeenCalled();
|
|
||||||
|
wrapper.update();
|
||||||
|
const updateObject = updateCaseProperty.mock.calls[0][0];
|
||||||
|
expect(updateObject.updateKey).toEqual('status');
|
||||||
|
expect(updateObject.updateValue).toEqual('closed');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -579,4 +619,90 @@ describe('CaseView ', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should show loading content when loading alerts', async () => {
|
||||||
|
useQueryAlertsMock.mockImplementation(() => ({
|
||||||
|
loading: true,
|
||||||
|
data: { hits: { hits: [] } },
|
||||||
|
}));
|
||||||
|
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<Router history={mockHistory}>
|
||||||
|
<CaseComponent {...caseProps} />
|
||||||
|
</Router>
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(
|
||||||
|
wrapper.find('[data-test-subj="case-view-loading-content"]').first().exists()
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(wrapper.find('[data-test-subj="user-actions"]').first().exists()).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should open the alert flyout', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<Router history={mockHistory}>
|
||||||
|
<CaseComponent {...caseProps} />
|
||||||
|
</Router>
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
wrapper
|
||||||
|
.find('[data-test-subj="comment-action-show-alert-alert-action-id"] button')
|
||||||
|
.first()
|
||||||
|
.simulate('click');
|
||||||
|
expect(mockDispatch).toHaveBeenCalledWith({
|
||||||
|
type: 'x-pack/security_solution/local/timeline/TOGGLE_EXPANDED_EVENT',
|
||||||
|
payload: {
|
||||||
|
event: { eventId: 'alert-id-1', indexName: 'alert-index-1' },
|
||||||
|
timelineId: 'timeline-case',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show the rule name', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<Router history={mockHistory}>
|
||||||
|
<CaseComponent {...caseProps} />
|
||||||
|
</Router>
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(
|
||||||
|
wrapper
|
||||||
|
.find(
|
||||||
|
'[data-test-subj="comment-create-action-alert-action-id"] .euiCommentEvent__headerEvent'
|
||||||
|
)
|
||||||
|
.first()
|
||||||
|
.text()
|
||||||
|
).toBe('added an alert from Awesome rule');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update settings', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<Router history={mockHistory}>
|
||||||
|
<CaseComponent {...caseProps} />
|
||||||
|
</Router>
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
wrapper.find('button[data-test-subj="sync-alerts-switch"]').first().simulate('click');
|
||||||
|
|
||||||
|
wrapper.update();
|
||||||
|
const updateObject = updateCaseProperty.mock.calls[0][0];
|
||||||
|
expect(updateObject.updateKey).toEqual('settings');
|
||||||
|
expect(updateObject.updateValue).toEqual({ syncAlerts: false });
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -429,7 +429,9 @@ export const CaseComponent = React.memo<CaseProps>(
|
||||||
{!initLoadingData && pushCallouts != null && pushCallouts}
|
{!initLoadingData && pushCallouts != null && pushCallouts}
|
||||||
<EuiFlexGroup>
|
<EuiFlexGroup>
|
||||||
<EuiFlexItem grow={6}>
|
<EuiFlexItem grow={6}>
|
||||||
{initLoadingData && <EuiLoadingContent lines={8} />}
|
{initLoadingData && (
|
||||||
|
<EuiLoadingContent lines={8} data-test-subj="case-view-loading-content" />
|
||||||
|
)}
|
||||||
{!initLoadingData && (
|
{!initLoadingData && (
|
||||||
<>
|
<>
|
||||||
<UserActionTree
|
<UserActionTree
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { Connector } from './connector';
|
||||||
import { useConnectors } from '../../containers/configure/use_connectors';
|
import { useConnectors } from '../../containers/configure/use_connectors';
|
||||||
import { useGetIncidentTypes } from '../settings/resilient/use_get_incident_types';
|
import { useGetIncidentTypes } from '../settings/resilient/use_get_incident_types';
|
||||||
import { useGetSeverity } from '../settings/resilient/use_get_severity';
|
import { useGetSeverity } from '../settings/resilient/use_get_severity';
|
||||||
|
import { schema, FormProps } from './schema';
|
||||||
|
|
||||||
jest.mock('../../../common/lib/kibana', () => {
|
jest.mock('../../../common/lib/kibana', () => {
|
||||||
return {
|
return {
|
||||||
|
@ -70,8 +71,12 @@ describe('Connector', () => {
|
||||||
let globalForm: FormHook;
|
let globalForm: FormHook;
|
||||||
|
|
||||||
const MockHookWrapperComponent: React.FC = ({ children }) => {
|
const MockHookWrapperComponent: React.FC = ({ children }) => {
|
||||||
const { form } = useForm<{ connectorId: string; fields: Record<string, unknown> | null }>({
|
const { form } = useForm<FormProps>({
|
||||||
defaultValue: { connectorId: connectorsMock[0].id, fields: null },
|
defaultValue: { connectorId: connectorsMock[0].id, fields: null },
|
||||||
|
schema: {
|
||||||
|
connectorId: schema.connectorId,
|
||||||
|
fields: schema.fields,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
globalForm = form;
|
globalForm = form;
|
||||||
|
@ -96,7 +101,14 @@ describe('Connector', () => {
|
||||||
expect(wrapper.find(`[data-test-subj="caseConnectors"]`).exists()).toBeTruthy();
|
expect(wrapper.find(`[data-test-subj="caseConnectors"]`).exists()).toBeTruthy();
|
||||||
expect(wrapper.find(`[data-test-subj="connector-settings"]`).exists()).toBeTruthy();
|
expect(wrapper.find(`[data-test-subj="connector-settings"]`).exists()).toBeTruthy();
|
||||||
|
|
||||||
waitFor(() => {
|
await waitFor(() => {
|
||||||
|
expect(wrapper.find(`button[data-test-subj="dropdown-connectors"]`).first().text()).toBe(
|
||||||
|
'My Connector'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
wrapper.update();
|
||||||
expect(wrapper.find(`[data-test-subj="connector-settings-sn"]`).exists()).toBeTruthy();
|
expect(wrapper.find(`[data-test-subj="connector-settings-sn"]`).exists()).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,13 +10,17 @@ import { act } from '@testing-library/react';
|
||||||
|
|
||||||
import { useForm, Form, FormHook } from '../../../shared_imports';
|
import { useForm, Form, FormHook } from '../../../shared_imports';
|
||||||
import { Description } from './description';
|
import { Description } from './description';
|
||||||
|
import { schema, FormProps } from './schema';
|
||||||
|
|
||||||
describe('Description', () => {
|
describe('Description', () => {
|
||||||
let globalForm: FormHook;
|
let globalForm: FormHook;
|
||||||
|
|
||||||
const MockHookWrapperComponent: React.FC = ({ children }) => {
|
const MockHookWrapperComponent: React.FC = ({ children }) => {
|
||||||
const { form } = useForm<{ description: string }>({
|
const { form } = useForm<FormProps>({
|
||||||
defaultValue: { description: 'My description' },
|
defaultValue: { description: 'My description' },
|
||||||
|
schema: {
|
||||||
|
description: schema.description,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
globalForm = form;
|
globalForm = form;
|
||||||
|
@ -41,7 +45,7 @@ describe('Description', () => {
|
||||||
it('it changes the description', async () => {
|
it('it changes the description', async () => {
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<MockHookWrapperComponent>
|
<MockHookWrapperComponent>
|
||||||
<Description isLoading={true} />
|
<Description isLoading={false} />
|
||||||
</MockHookWrapperComponent>
|
</MockHookWrapperComponent>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,9 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
|
import { act, waitFor } from '@testing-library/react';
|
||||||
|
|
||||||
import { useForm, Form } from '../../../shared_imports';
|
import { useForm, Form, FormHook } from '../../../shared_imports';
|
||||||
import { useGetTags } from '../../containers/use_get_tags';
|
import { useGetTags } from '../../containers/use_get_tags';
|
||||||
import { useConnectors } from '../../containers/configure/use_connectors';
|
import { useConnectors } from '../../containers/configure/use_connectors';
|
||||||
import { connectorsMock } from '../../containers/mock';
|
import { connectorsMock } from '../../containers/mock';
|
||||||
|
@ -29,6 +30,7 @@ const initialCaseValue: FormProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('CreateCaseForm', () => {
|
describe('CreateCaseForm', () => {
|
||||||
|
let globalForm: FormHook;
|
||||||
const MockHookWrapperComponent: React.FC = ({ children }) => {
|
const MockHookWrapperComponent: React.FC = ({ children }) => {
|
||||||
const { form } = useForm<FormProps>({
|
const { form } = useForm<FormProps>({
|
||||||
defaultValue: initialCaseValue,
|
defaultValue: initialCaseValue,
|
||||||
|
@ -36,6 +38,8 @@ describe('CreateCaseForm', () => {
|
||||||
schema,
|
schema,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
globalForm = form;
|
||||||
|
|
||||||
return <Form form={form}>{children}</Form>;
|
return <Form form={form}>{children}</Form>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -64,4 +68,41 @@ describe('CreateCaseForm', () => {
|
||||||
|
|
||||||
expect(wrapper.find(`[data-test-subj="case-creation-form-steps"]`).exists()).toBeFalsy();
|
expect(wrapper.find(`[data-test-subj="case-creation-form-steps"]`).exists()).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('it renders all form fields', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<MockHookWrapperComponent>
|
||||||
|
<CreateCaseForm />
|
||||||
|
</MockHookWrapperComponent>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find(`[data-test-subj="caseTitle"]`).exists()).toBeTruthy();
|
||||||
|
expect(wrapper.find(`[data-test-subj="caseTags"]`).exists()).toBeTruthy();
|
||||||
|
expect(wrapper.find(`[data-test-subj="caseDescription"]`).exists()).toBeTruthy();
|
||||||
|
expect(wrapper.find(`[data-test-subj="caseSyncAlerts"]`).exists()).toBeTruthy();
|
||||||
|
expect(wrapper.find(`[data-test-subj="caseConnectors"]`).exists()).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render spinner when loading', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<MockHookWrapperComponent>
|
||||||
|
<CreateCaseForm />
|
||||||
|
</MockHookWrapperComponent>
|
||||||
|
);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
globalForm.setFieldValue('title', 'title');
|
||||||
|
globalForm.setFieldValue('description', 'description');
|
||||||
|
globalForm.submit();
|
||||||
|
// For some weird reason this is needed to pass the test.
|
||||||
|
// It does not do anything useful
|
||||||
|
await wrapper.find(`[data-test-subj="caseTitle"]`);
|
||||||
|
await wrapper.update();
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(
|
||||||
|
wrapper.find(`[data-test-subj="create-case-loading-spinner"]`).exists()
|
||||||
|
).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,420 @@
|
||||||
|
/*
|
||||||
|
* 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 React from 'react';
|
||||||
|
import { mount, ReactWrapper } from 'enzyme';
|
||||||
|
import { act, waitFor } from '@testing-library/react';
|
||||||
|
import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
|
||||||
|
|
||||||
|
import { ConnectorTypes } from '../../../../../case/common/api';
|
||||||
|
import { TestProviders } from '../../../common/mock';
|
||||||
|
import { usePostCase } from '../../containers/use_post_case';
|
||||||
|
import { useGetTags } from '../../containers/use_get_tags';
|
||||||
|
import { useConnectors } from '../../containers/configure/use_connectors';
|
||||||
|
import { useCaseConfigure } from '../../containers/configure/use_configure';
|
||||||
|
import { connectorsMock } from '../../containers/configure/mock';
|
||||||
|
import { useGetIncidentTypes } from '../settings/resilient/use_get_incident_types';
|
||||||
|
import { useGetSeverity } from '../settings/resilient/use_get_severity';
|
||||||
|
import { useGetIssueTypes } from '../settings/jira/use_get_issue_types';
|
||||||
|
import { useGetFieldsByIssueType } from '../settings/jira/use_get_fields_by_issue_type';
|
||||||
|
import { useCaseConfigureResponse } from '../configure_cases/__mock__';
|
||||||
|
import {
|
||||||
|
sampleConnectorData,
|
||||||
|
sampleData,
|
||||||
|
sampleTags,
|
||||||
|
useGetIncidentTypesResponse,
|
||||||
|
useGetSeverityResponse,
|
||||||
|
useGetIssueTypesResponse,
|
||||||
|
useGetFieldsByIssueTypeResponse,
|
||||||
|
} from './mock';
|
||||||
|
import { FormContext } from './form_context';
|
||||||
|
import { CreateCaseForm } from './form';
|
||||||
|
import { SubmitCaseButton } from './submit_button';
|
||||||
|
|
||||||
|
jest.mock('../../containers/use_post_case');
|
||||||
|
jest.mock('../../containers/use_get_tags');
|
||||||
|
jest.mock('../../containers/configure/use_connectors');
|
||||||
|
jest.mock('../../containers/configure/use_configure');
|
||||||
|
jest.mock('../settings/resilient/use_get_incident_types');
|
||||||
|
jest.mock('../settings/resilient/use_get_severity');
|
||||||
|
jest.mock('../settings/jira/use_get_issue_types');
|
||||||
|
jest.mock('../settings/jira/use_get_fields_by_issue_type');
|
||||||
|
jest.mock('../settings/jira/use_get_single_issue');
|
||||||
|
jest.mock('../settings/jira/use_get_issues');
|
||||||
|
|
||||||
|
const useConnectorsMock = useConnectors as jest.Mock;
|
||||||
|
const useCaseConfigureMock = useCaseConfigure as jest.Mock;
|
||||||
|
const usePostCaseMock = usePostCase as jest.Mock;
|
||||||
|
const useGetIncidentTypesMock = useGetIncidentTypes as jest.Mock;
|
||||||
|
const useGetSeverityMock = useGetSeverity as jest.Mock;
|
||||||
|
const useGetIssueTypesMock = useGetIssueTypes as jest.Mock;
|
||||||
|
const useGetFieldsByIssueTypeMock = useGetFieldsByIssueType as jest.Mock;
|
||||||
|
const postCase = jest.fn();
|
||||||
|
|
||||||
|
const defaultPostCase = {
|
||||||
|
isLoading: false,
|
||||||
|
isError: false,
|
||||||
|
caseData: null,
|
||||||
|
postCase,
|
||||||
|
};
|
||||||
|
|
||||||
|
const fillForm = (wrapper: ReactWrapper) => {
|
||||||
|
wrapper
|
||||||
|
.find(`[data-test-subj="caseTitle"] input`)
|
||||||
|
.first()
|
||||||
|
.simulate('change', { target: { value: sampleData.title } });
|
||||||
|
|
||||||
|
wrapper
|
||||||
|
.find(`[data-test-subj="caseDescription"] textarea`)
|
||||||
|
.first()
|
||||||
|
.simulate('change', { target: { value: sampleData.description } });
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
((wrapper.find(EuiComboBox).props() as unknown) as {
|
||||||
|
onChange: (a: EuiComboBoxOptionOption[]) => void;
|
||||||
|
}).onChange(sampleTags.map((tag) => ({ label: tag })));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Create case', () => {
|
||||||
|
const fetchTags = jest.fn();
|
||||||
|
const onFormSubmitSuccess = jest.fn();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
usePostCaseMock.mockImplementation(() => defaultPostCase);
|
||||||
|
useConnectorsMock.mockReturnValue(sampleConnectorData);
|
||||||
|
useCaseConfigureMock.mockImplementation(() => useCaseConfigureResponse);
|
||||||
|
useGetIncidentTypesMock.mockReturnValue(useGetIncidentTypesResponse);
|
||||||
|
useGetSeverityMock.mockReturnValue(useGetSeverityResponse);
|
||||||
|
useGetIssueTypesMock.mockReturnValue(useGetIssueTypesResponse);
|
||||||
|
useGetFieldsByIssueTypeMock.mockReturnValue(useGetFieldsByIssueTypeResponse);
|
||||||
|
|
||||||
|
(useGetTags as jest.Mock).mockImplementation(() => ({
|
||||||
|
tags: sampleTags,
|
||||||
|
fetchTags,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Step 1 - Case Fields', () => {
|
||||||
|
it('it renders', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<FormContext onSuccess={onFormSubmitSuccess}>
|
||||||
|
<CreateCaseForm />
|
||||||
|
<SubmitCaseButton />
|
||||||
|
</FormContext>
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find(`[data-test-subj="caseTitle"]`).first().exists()).toBeTruthy();
|
||||||
|
expect(wrapper.find(`[data-test-subj="caseDescription"]`).first().exists()).toBeTruthy();
|
||||||
|
expect(wrapper.find(`[data-test-subj="caseTags"]`).first().exists()).toBeTruthy();
|
||||||
|
expect(wrapper.find(`[data-test-subj="caseConnectors"]`).first().exists()).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
wrapper.find(`[data-test-subj="case-creation-form-steps"]`).first().exists()
|
||||||
|
).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should post case on submit click', async () => {
|
||||||
|
useConnectorsMock.mockReturnValue({
|
||||||
|
...sampleConnectorData,
|
||||||
|
connectors: connectorsMock,
|
||||||
|
});
|
||||||
|
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<FormContext onSuccess={onFormSubmitSuccess}>
|
||||||
|
<CreateCaseForm />
|
||||||
|
<SubmitCaseButton />
|
||||||
|
</FormContext>
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
fillForm(wrapper);
|
||||||
|
wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click');
|
||||||
|
await waitFor(() => expect(postCase).toBeCalledWith(sampleData));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should toggle sync settings', async () => {
|
||||||
|
useConnectorsMock.mockReturnValue({
|
||||||
|
...sampleConnectorData,
|
||||||
|
connectors: connectorsMock,
|
||||||
|
});
|
||||||
|
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<FormContext onSuccess={onFormSubmitSuccess}>
|
||||||
|
<CreateCaseForm />
|
||||||
|
<SubmitCaseButton />
|
||||||
|
</FormContext>
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
fillForm(wrapper);
|
||||||
|
wrapper.find('[data-test-subj="caseSyncAlerts"] button').first().simulate('click');
|
||||||
|
wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click');
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(postCase).toBeCalledWith({ ...sampleData, settings: { syncAlerts: false } })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should redirect to new case when caseData is there', async () => {
|
||||||
|
const sampleId = 'case-id';
|
||||||
|
usePostCaseMock.mockImplementation(() => ({
|
||||||
|
...defaultPostCase,
|
||||||
|
caseData: { id: sampleId },
|
||||||
|
}));
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<TestProviders>
|
||||||
|
<FormContext onSuccess={onFormSubmitSuccess}>
|
||||||
|
<CreateCaseForm />
|
||||||
|
<SubmitCaseButton />
|
||||||
|
</FormContext>
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => expect(onFormSubmitSuccess).toHaveBeenCalledWith({ id: 'case-id' }));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it should select the default connector set in the configuration', async () => {
|
||||||
|
useCaseConfigureMock.mockImplementation(() => ({
|
||||||
|
...useCaseConfigureResponse,
|
||||||
|
connector: {
|
||||||
|
id: 'servicenow-1',
|
||||||
|
name: 'SN',
|
||||||
|
type: ConnectorTypes.servicenow,
|
||||||
|
fields: null,
|
||||||
|
},
|
||||||
|
persistLoading: false,
|
||||||
|
}));
|
||||||
|
|
||||||
|
useConnectorsMock.mockReturnValue({
|
||||||
|
...sampleConnectorData,
|
||||||
|
connectors: connectorsMock,
|
||||||
|
});
|
||||||
|
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<FormContext onSuccess={onFormSubmitSuccess}>
|
||||||
|
<CreateCaseForm />
|
||||||
|
<SubmitCaseButton />
|
||||||
|
</FormContext>
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
fillForm(wrapper);
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click');
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(postCase).toBeCalledWith({
|
||||||
|
...sampleData,
|
||||||
|
connector: {
|
||||||
|
fields: {
|
||||||
|
impact: null,
|
||||||
|
severity: null,
|
||||||
|
urgency: null,
|
||||||
|
},
|
||||||
|
id: 'servicenow-1',
|
||||||
|
name: 'My Connector',
|
||||||
|
type: '.servicenow',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it should default to none if the default connector does not exist in connectors', async () => {
|
||||||
|
useCaseConfigureMock.mockImplementation(() => ({
|
||||||
|
...useCaseConfigureResponse,
|
||||||
|
connector: {
|
||||||
|
id: 'not-exist',
|
||||||
|
name: 'SN',
|
||||||
|
type: ConnectorTypes.servicenow,
|
||||||
|
fields: null,
|
||||||
|
},
|
||||||
|
persistLoading: false,
|
||||||
|
}));
|
||||||
|
|
||||||
|
useConnectorsMock.mockReturnValue({
|
||||||
|
...sampleConnectorData,
|
||||||
|
connectors: connectorsMock,
|
||||||
|
});
|
||||||
|
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<FormContext onSuccess={onFormSubmitSuccess}>
|
||||||
|
<CreateCaseForm />
|
||||||
|
<SubmitCaseButton />
|
||||||
|
</FormContext>
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
fillForm(wrapper);
|
||||||
|
wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click');
|
||||||
|
await waitFor(() => expect(postCase).toBeCalledWith(sampleData));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Step 2 - Connector Fields', () => {
|
||||||
|
it(`it should submit a Jira connector`, async () => {
|
||||||
|
useConnectorsMock.mockReturnValue({
|
||||||
|
...sampleConnectorData,
|
||||||
|
connectors: connectorsMock,
|
||||||
|
});
|
||||||
|
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<FormContext onSuccess={onFormSubmitSuccess}>
|
||||||
|
<CreateCaseForm />
|
||||||
|
<SubmitCaseButton />
|
||||||
|
</FormContext>
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
fillForm(wrapper);
|
||||||
|
expect(wrapper.find(`[data-test-subj="connector-settings-jira"]`).exists()).toBeFalsy();
|
||||||
|
wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click');
|
||||||
|
wrapper.find(`button[data-test-subj="dropdown-connector-jira-1"]`).simulate('click');
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
wrapper.update();
|
||||||
|
expect(wrapper.find(`[data-test-subj="connector-settings-jira"]`).exists()).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper
|
||||||
|
.find('select[data-test-subj="issueTypeSelect"]')
|
||||||
|
.first()
|
||||||
|
.simulate('change', {
|
||||||
|
target: { value: '10007' },
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper
|
||||||
|
.find('select[data-test-subj="prioritySelect"]')
|
||||||
|
.first()
|
||||||
|
.simulate('change', {
|
||||||
|
target: { value: '2' },
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click');
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(postCase).toBeCalledWith({
|
||||||
|
...sampleData,
|
||||||
|
connector: {
|
||||||
|
id: 'jira-1',
|
||||||
|
name: 'Jira',
|
||||||
|
type: '.jira',
|
||||||
|
fields: { issueType: '10007', parent: null, priority: '2' },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`it should submit a resilient connector`, async () => {
|
||||||
|
useConnectorsMock.mockReturnValue({
|
||||||
|
...sampleConnectorData,
|
||||||
|
connectors: connectorsMock,
|
||||||
|
});
|
||||||
|
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<FormContext onSuccess={onFormSubmitSuccess}>
|
||||||
|
<CreateCaseForm />
|
||||||
|
<SubmitCaseButton />
|
||||||
|
</FormContext>
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
fillForm(wrapper);
|
||||||
|
expect(wrapper.find(`[data-test-subj="connector-settings-resilient"]`).exists()).toBeFalsy();
|
||||||
|
wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click');
|
||||||
|
wrapper.find(`button[data-test-subj="dropdown-connector-resilient-2"]`).simulate('click');
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
wrapper.update();
|
||||||
|
expect(
|
||||||
|
wrapper.find(`[data-test-subj="connector-settings-resilient"]`).exists()
|
||||||
|
).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
((wrapper.find(EuiComboBox).at(1).props() as unknown) as {
|
||||||
|
onChange: (a: EuiComboBoxOptionOption[]) => void;
|
||||||
|
}).onChange([{ value: '19', label: 'Denial of Service' }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper
|
||||||
|
.find('select[data-test-subj="severitySelect"]')
|
||||||
|
.first()
|
||||||
|
.simulate('change', {
|
||||||
|
target: { value: '4' },
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click');
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(postCase).toBeCalledWith({
|
||||||
|
...sampleData,
|
||||||
|
connector: {
|
||||||
|
id: 'resilient-2',
|
||||||
|
name: 'My Connector 2',
|
||||||
|
type: '.resilient',
|
||||||
|
fields: { incidentTypes: ['19'], severityCode: '4' },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`it should submit a servicenow connector`, async () => {
|
||||||
|
useConnectorsMock.mockReturnValue({
|
||||||
|
...sampleConnectorData,
|
||||||
|
connectors: connectorsMock,
|
||||||
|
});
|
||||||
|
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<FormContext onSuccess={onFormSubmitSuccess}>
|
||||||
|
<CreateCaseForm />
|
||||||
|
<SubmitCaseButton />
|
||||||
|
</FormContext>
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
fillForm(wrapper);
|
||||||
|
expect(wrapper.find(`[data-test-subj="connector-settings-sn"]`).exists()).toBeFalsy();
|
||||||
|
wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click');
|
||||||
|
wrapper.find(`button[data-test-subj="dropdown-connector-servicenow-1"]`).simulate('click');
|
||||||
|
expect(wrapper.find(`[data-test-subj="connector-settings-sn"]`).exists()).toBeTruthy();
|
||||||
|
|
||||||
|
['severitySelect', 'urgencySelect', 'impactSelect'].forEach((subj) => {
|
||||||
|
wrapper
|
||||||
|
.find(`select[data-test-subj="${subj}"]`)
|
||||||
|
.first()
|
||||||
|
.simulate('change', {
|
||||||
|
target: { value: '2' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click');
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(postCase).toBeCalledWith({
|
||||||
|
...sampleData,
|
||||||
|
connector: {
|
||||||
|
id: 'servicenow-1',
|
||||||
|
name: 'My Connector',
|
||||||
|
type: '.servicenow',
|
||||||
|
fields: { impact: '2', severity: '2', urgency: '2' },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -7,25 +7,32 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mount, ReactWrapper } from 'enzyme';
|
import { mount, ReactWrapper } from 'enzyme';
|
||||||
import { act, waitFor } from '@testing-library/react';
|
import { act, waitFor } from '@testing-library/react';
|
||||||
|
import { noop } from 'lodash/fp';
|
||||||
import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
|
import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
|
||||||
|
|
||||||
import { CasePostRequest } from '../../../../../case/common/api';
|
|
||||||
import { TestProviders } from '../../../common/mock';
|
import { TestProviders } from '../../../common/mock';
|
||||||
import { usePostCase } from '../../containers/use_post_case';
|
|
||||||
import { useGetTags } from '../../containers/use_get_tags';
|
import { useGetTags } from '../../containers/use_get_tags';
|
||||||
import { useConnectors } from '../../containers/configure/use_connectors';
|
import { useConnectors } from '../../containers/configure/use_connectors';
|
||||||
import { useCaseConfigure } from '../../containers/configure/use_configure';
|
import { useCaseConfigure } from '../../containers/configure/use_configure';
|
||||||
import { connectorsMock } from '../../containers/configure/mock';
|
|
||||||
import { ConnectorTypes } from '../../../../../case/common/api/connectors';
|
|
||||||
import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router';
|
import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router';
|
||||||
import { useGetIncidentTypes } from '../settings/resilient/use_get_incident_types';
|
import { useGetIncidentTypes } from '../settings/resilient/use_get_incident_types';
|
||||||
import { useGetSeverity } from '../settings/resilient/use_get_severity';
|
import { useGetSeverity } from '../settings/resilient/use_get_severity';
|
||||||
import { useGetIssueTypes } from '../settings/jira/use_get_issue_types';
|
import { useGetIssueTypes } from '../settings/jira/use_get_issue_types';
|
||||||
import { useGetFieldsByIssueType } from '../settings/jira/use_get_fields_by_issue_type';
|
import { useGetFieldsByIssueType } from '../settings/jira/use_get_fields_by_issue_type';
|
||||||
import { useCaseConfigureResponse } from '../configure_cases/__mock__';
|
import { useCaseConfigureResponse } from '../configure_cases/__mock__';
|
||||||
|
import { useInsertTimeline } from '../use_insert_timeline';
|
||||||
|
import {
|
||||||
|
sampleConnectorData,
|
||||||
|
sampleData,
|
||||||
|
sampleTags,
|
||||||
|
useGetIncidentTypesResponse,
|
||||||
|
useGetSeverityResponse,
|
||||||
|
useGetIssueTypesResponse,
|
||||||
|
useGetFieldsByIssueTypeResponse,
|
||||||
|
} from './mock';
|
||||||
import { Create } from '.';
|
import { Create } from '.';
|
||||||
|
|
||||||
jest.mock('../../containers/use_post_case');
|
jest.mock('../../containers/api');
|
||||||
jest.mock('../../containers/use_get_tags');
|
jest.mock('../../containers/use_get_tags');
|
||||||
jest.mock('../../containers/configure/use_connectors');
|
jest.mock('../../containers/configure/use_connectors');
|
||||||
jest.mock('../../containers/configure/use_configure');
|
jest.mock('../../containers/configure/use_configure');
|
||||||
|
@ -35,125 +42,30 @@ jest.mock('../settings/jira/use_get_issue_types');
|
||||||
jest.mock('../settings/jira/use_get_fields_by_issue_type');
|
jest.mock('../settings/jira/use_get_fields_by_issue_type');
|
||||||
jest.mock('../settings/jira/use_get_single_issue');
|
jest.mock('../settings/jira/use_get_single_issue');
|
||||||
jest.mock('../settings/jira/use_get_issues');
|
jest.mock('../settings/jira/use_get_issues');
|
||||||
|
jest.mock('../use_insert_timeline');
|
||||||
|
|
||||||
const useConnectorsMock = useConnectors as jest.Mock;
|
const useConnectorsMock = useConnectors as jest.Mock;
|
||||||
const useCaseConfigureMock = useCaseConfigure as jest.Mock;
|
const useCaseConfigureMock = useCaseConfigure as jest.Mock;
|
||||||
const usePostCaseMock = usePostCase as jest.Mock;
|
const useGetTagsMock = useGetTags as jest.Mock;
|
||||||
const useGetIncidentTypesMock = useGetIncidentTypes as jest.Mock;
|
const useGetIncidentTypesMock = useGetIncidentTypes as jest.Mock;
|
||||||
const useGetSeverityMock = useGetSeverity as jest.Mock;
|
const useGetSeverityMock = useGetSeverity as jest.Mock;
|
||||||
const useGetIssueTypesMock = useGetIssueTypes as jest.Mock;
|
const useGetIssueTypesMock = useGetIssueTypes as jest.Mock;
|
||||||
const useGetFieldsByIssueTypeMock = useGetFieldsByIssueType as jest.Mock;
|
const useGetFieldsByIssueTypeMock = useGetFieldsByIssueType as jest.Mock;
|
||||||
const postCase = jest.fn();
|
const useInsertTimelineMock = useInsertTimeline as jest.Mock;
|
||||||
|
const fetchTags = jest.fn();
|
||||||
|
|
||||||
const sampleTags = ['coke', 'pepsi'];
|
const fillForm = (wrapper: ReactWrapper) => {
|
||||||
const sampleData: CasePostRequest = {
|
wrapper
|
||||||
description: 'what a great description',
|
.find(`[data-test-subj="caseTitle"] input`)
|
||||||
tags: sampleTags,
|
.first()
|
||||||
title: 'what a cool title',
|
.simulate('change', { target: { value: sampleData.title } });
|
||||||
connector: {
|
|
||||||
fields: null,
|
|
||||||
id: 'none',
|
|
||||||
name: 'none',
|
|
||||||
type: ConnectorTypes.none,
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
syncAlerts: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultPostCase = {
|
wrapper
|
||||||
isLoading: false,
|
.find(`[data-test-subj="caseDescription"] textarea`)
|
||||||
isError: false,
|
.first()
|
||||||
caseData: null,
|
.simulate('change', { target: { value: sampleData.description } });
|
||||||
postCase,
|
|
||||||
};
|
|
||||||
|
|
||||||
const sampleConnectorData = { loading: false, connectors: [] };
|
act(() => {
|
||||||
|
|
||||||
const useGetIncidentTypesResponse = {
|
|
||||||
isLoading: false,
|
|
||||||
incidentTypes: [
|
|
||||||
{
|
|
||||||
id: 19,
|
|
||||||
name: 'Malware',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 21,
|
|
||||||
name: 'Denial of Service',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const useGetSeverityResponse = {
|
|
||||||
isLoading: false,
|
|
||||||
severity: [
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
name: 'Low',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
name: 'Medium',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 6,
|
|
||||||
name: 'High',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const useGetIssueTypesResponse = {
|
|
||||||
isLoading: false,
|
|
||||||
issueTypes: [
|
|
||||||
{
|
|
||||||
id: '10006',
|
|
||||||
name: 'Task',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '10007',
|
|
||||||
name: 'Bug',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const useGetFieldsByIssueTypeResponse = {
|
|
||||||
isLoading: false,
|
|
||||||
fields: {
|
|
||||||
summary: { allowedValues: [], defaultValue: {} },
|
|
||||||
labels: { allowedValues: [], defaultValue: {} },
|
|
||||||
description: { allowedValues: [], defaultValue: {} },
|
|
||||||
priority: {
|
|
||||||
allowedValues: [
|
|
||||||
{
|
|
||||||
name: 'Medium',
|
|
||||||
id: '3',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Low',
|
|
||||||
id: '2',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
defaultValue: { name: 'Medium', id: '3' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const fillForm = async (wrapper: ReactWrapper) => {
|
|
||||||
await act(async () => {
|
|
||||||
wrapper
|
|
||||||
.find(`[data-test-subj="caseTitle"] input`)
|
|
||||||
.first()
|
|
||||||
.simulate('change', { target: { value: sampleData.title } });
|
|
||||||
});
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
wrapper
|
|
||||||
.find(`[data-test-subj="caseDescription"] textarea`)
|
|
||||||
.first()
|
|
||||||
.simulate('change', { target: { value: sampleData.description } });
|
|
||||||
});
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
((wrapper.find(EuiComboBox).props() as unknown) as {
|
((wrapper.find(EuiComboBox).props() as unknown) as {
|
||||||
onChange: (a: EuiComboBoxOptionOption[]) => void;
|
onChange: (a: EuiComboBoxOptionOption[]) => void;
|
||||||
}).onChange(sampleTags.map((tag) => ({ label: tag })));
|
}).onChange(sampleTags.map((tag) => ({ label: tag })));
|
||||||
|
@ -161,381 +73,83 @@ const fillForm = async (wrapper: ReactWrapper) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('Create case', () => {
|
describe('Create case', () => {
|
||||||
const fetchTags = jest.fn();
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.resetAllMocks();
|
jest.resetAllMocks();
|
||||||
usePostCaseMock.mockImplementation(() => defaultPostCase);
|
jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation);
|
||||||
useConnectorsMock.mockReturnValue(sampleConnectorData);
|
useConnectorsMock.mockReturnValue(sampleConnectorData);
|
||||||
useCaseConfigureMock.mockImplementation(() => useCaseConfigureResponse);
|
useCaseConfigureMock.mockImplementation(() => useCaseConfigureResponse);
|
||||||
useGetIncidentTypesMock.mockReturnValue(useGetIncidentTypesResponse);
|
useGetIncidentTypesMock.mockReturnValue(useGetIncidentTypesResponse);
|
||||||
useGetSeverityMock.mockReturnValue(useGetSeverityResponse);
|
useGetSeverityMock.mockReturnValue(useGetSeverityResponse);
|
||||||
useGetIssueTypesMock.mockReturnValue(useGetIssueTypesResponse);
|
useGetIssueTypesMock.mockReturnValue(useGetIssueTypesResponse);
|
||||||
useGetFieldsByIssueTypeMock.mockReturnValue(useGetFieldsByIssueTypeResponse);
|
useGetFieldsByIssueTypeMock.mockReturnValue(useGetFieldsByIssueTypeResponse);
|
||||||
|
useGetTagsMock.mockImplementation(() => ({
|
||||||
jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation);
|
|
||||||
(useGetTags as jest.Mock).mockImplementation(() => ({
|
|
||||||
tags: sampleTags,
|
tags: sampleTags,
|
||||||
fetchTags,
|
fetchTags,
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Step 1 - Case Fields', () => {
|
it('it renders', async () => {
|
||||||
it('it renders', async () => {
|
const wrapper = mount(
|
||||||
const wrapper = mount(
|
<TestProviders>
|
||||||
<TestProviders>
|
<Router history={mockHistory}>
|
||||||
<Router history={mockHistory}>
|
<Create />
|
||||||
<Create />
|
</Router>
|
||||||
</Router>
|
</TestProviders>
|
||||||
</TestProviders>
|
);
|
||||||
);
|
|
||||||
|
|
||||||
expect(wrapper.find(`[data-test-subj="caseTitle"]`).first().exists()).toBeTruthy();
|
expect(wrapper.find(`[data-test-subj="create-case-submit"]`).exists()).toBeTruthy();
|
||||||
expect(wrapper.find(`[data-test-subj="caseDescription"]`).first().exists()).toBeTruthy();
|
expect(wrapper.find(`[data-test-subj="create-case-cancel"]`).exists()).toBeTruthy();
|
||||||
expect(wrapper.find(`[data-test-subj="caseTags"]`).first().exists()).toBeTruthy();
|
|
||||||
expect(wrapper.find(`[data-test-subj="caseConnectors"]`).first().exists()).toBeTruthy();
|
|
||||||
expect(wrapper.find(`[data-test-subj="create-case-submit"]`).first().exists()).toBeTruthy();
|
|
||||||
expect(wrapper.find(`[data-test-subj="create-case-cancel"]`).first().exists()).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
wrapper.find(`[data-test-subj="case-creation-form-steps"]`).first().exists()
|
|
||||||
).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should post case on submit click', async () => {
|
|
||||||
useConnectorsMock.mockReturnValue({
|
|
||||||
...sampleConnectorData,
|
|
||||||
connectors: connectorsMock,
|
|
||||||
});
|
|
||||||
|
|
||||||
const wrapper = mount(
|
|
||||||
<TestProviders>
|
|
||||||
<Router history={mockHistory}>
|
|
||||||
<Create />
|
|
||||||
</Router>
|
|
||||||
</TestProviders>
|
|
||||||
);
|
|
||||||
|
|
||||||
await fillForm(wrapper);
|
|
||||||
wrapper.update();
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click');
|
|
||||||
});
|
|
||||||
await waitFor(() => expect(postCase).toBeCalledWith(sampleData));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should redirect to all cases on cancel click', async () => {
|
|
||||||
const wrapper = mount(
|
|
||||||
<TestProviders>
|
|
||||||
<Router history={mockHistory}>
|
|
||||||
<Create />
|
|
||||||
</Router>
|
|
||||||
</TestProviders>
|
|
||||||
);
|
|
||||||
|
|
||||||
wrapper.find(`[data-test-subj="create-case-cancel"]`).first().simulate('click');
|
|
||||||
await waitFor(() => expect(mockHistory.push).toHaveBeenCalledWith('/'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should redirect to new case when caseData is there', async () => {
|
|
||||||
const sampleId = 'case-id';
|
|
||||||
usePostCaseMock.mockImplementation(() => ({
|
|
||||||
...defaultPostCase,
|
|
||||||
caseData: { id: sampleId },
|
|
||||||
}));
|
|
||||||
|
|
||||||
mount(
|
|
||||||
<TestProviders>
|
|
||||||
<Router history={mockHistory}>
|
|
||||||
<Create />
|
|
||||||
</Router>
|
|
||||||
</TestProviders>
|
|
||||||
);
|
|
||||||
|
|
||||||
await waitFor(() => expect(mockHistory.push).toHaveBeenNthCalledWith(1, '/case-id'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render spinner when loading', async () => {
|
|
||||||
const wrapper = mount(
|
|
||||||
<TestProviders>
|
|
||||||
<Router history={mockHistory}>
|
|
||||||
<Create />
|
|
||||||
</Router>
|
|
||||||
</TestProviders>
|
|
||||||
);
|
|
||||||
|
|
||||||
await fillForm(wrapper);
|
|
||||||
await act(async () => {
|
|
||||||
await wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click');
|
|
||||||
wrapper.update();
|
|
||||||
expect(
|
|
||||||
wrapper.find(`[data-test-subj="create-case-loading-spinner"]`).exists()
|
|
||||||
).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('it should select the default connector set in the configuration', async () => {
|
|
||||||
useCaseConfigureMock.mockImplementation(() => ({
|
|
||||||
...useCaseConfigureResponse,
|
|
||||||
connector: {
|
|
||||||
id: 'servicenow-1',
|
|
||||||
name: 'SN',
|
|
||||||
type: ConnectorTypes.servicenow,
|
|
||||||
fields: null,
|
|
||||||
},
|
|
||||||
persistLoading: false,
|
|
||||||
}));
|
|
||||||
|
|
||||||
useConnectorsMock.mockReturnValue({
|
|
||||||
...sampleConnectorData,
|
|
||||||
connectors: connectorsMock,
|
|
||||||
});
|
|
||||||
|
|
||||||
const wrapper = mount(
|
|
||||||
<TestProviders>
|
|
||||||
<Router history={mockHistory}>
|
|
||||||
<Create />
|
|
||||||
</Router>
|
|
||||||
</TestProviders>
|
|
||||||
);
|
|
||||||
|
|
||||||
await fillForm(wrapper);
|
|
||||||
wrapper.update();
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click');
|
|
||||||
});
|
|
||||||
|
|
||||||
await waitFor(() =>
|
|
||||||
expect(postCase).toBeCalledWith({
|
|
||||||
...sampleData,
|
|
||||||
connector: {
|
|
||||||
fields: {
|
|
||||||
impact: null,
|
|
||||||
severity: null,
|
|
||||||
urgency: null,
|
|
||||||
},
|
|
||||||
id: 'servicenow-1',
|
|
||||||
name: 'My Connector',
|
|
||||||
type: '.servicenow',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('it should default to none if the default connector does not exist in connectors', async () => {
|
|
||||||
useCaseConfigureMock.mockImplementation(() => ({
|
|
||||||
...useCaseConfigureResponse,
|
|
||||||
connector: {
|
|
||||||
id: 'not-exist',
|
|
||||||
name: 'SN',
|
|
||||||
type: ConnectorTypes.servicenow,
|
|
||||||
fields: null,
|
|
||||||
},
|
|
||||||
persistLoading: false,
|
|
||||||
}));
|
|
||||||
|
|
||||||
useConnectorsMock.mockReturnValue({
|
|
||||||
...sampleConnectorData,
|
|
||||||
connectors: connectorsMock,
|
|
||||||
});
|
|
||||||
|
|
||||||
const wrapper = mount(
|
|
||||||
<TestProviders>
|
|
||||||
<Router history={mockHistory}>
|
|
||||||
<Create />
|
|
||||||
</Router>
|
|
||||||
</TestProviders>
|
|
||||||
);
|
|
||||||
|
|
||||||
await fillForm(wrapper);
|
|
||||||
wrapper.update();
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click');
|
|
||||||
});
|
|
||||||
|
|
||||||
await waitFor(() => expect(postCase).toBeCalledWith(sampleData));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Step 2 - Connector Fields', () => {
|
it('should redirect to all cases on cancel click', async () => {
|
||||||
it(`it should submit a Jira connector`, async () => {
|
const wrapper = mount(
|
||||||
useConnectorsMock.mockReturnValue({
|
<TestProviders>
|
||||||
...sampleConnectorData,
|
<Router history={mockHistory}>
|
||||||
connectors: connectorsMock,
|
<Create />
|
||||||
});
|
</Router>
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
const wrapper = mount(
|
wrapper.find(`[data-test-subj="create-case-cancel"]`).first().simulate('click');
|
||||||
<TestProviders>
|
await waitFor(() => expect(mockHistory.push).toHaveBeenCalledWith('/'));
|
||||||
<Router history={mockHistory}>
|
});
|
||||||
<Create />
|
|
||||||
</Router>
|
|
||||||
</TestProviders>
|
|
||||||
);
|
|
||||||
|
|
||||||
await fillForm(wrapper);
|
it('should redirect to new case when posting the case', async () => {
|
||||||
await waitFor(() => {
|
const wrapper = mount(
|
||||||
expect(wrapper.find(`[data-test-subj="connector-settings-jira"]`).exists()).toBeFalsy();
|
<TestProviders>
|
||||||
wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click');
|
<Router history={mockHistory}>
|
||||||
wrapper.find(`button[data-test-subj="dropdown-connector-jira-1"]`).simulate('click');
|
<Create />
|
||||||
wrapper.update();
|
</Router>
|
||||||
});
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
await waitFor(() => {
|
fillForm(wrapper);
|
||||||
wrapper.update();
|
wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click');
|
||||||
expect(wrapper.find(`[data-test-subj="connector-settings-jira"]`).exists()).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
act(() => {
|
await waitFor(() => expect(mockHistory.push).toHaveBeenNthCalledWith(1, '/basic-case-id'));
|
||||||
wrapper
|
});
|
||||||
.find('select[data-test-subj="issueTypeSelect"]')
|
|
||||||
.first()
|
|
||||||
.simulate('change', {
|
|
||||||
target: { value: '10007' },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
act(() => {
|
it('it should insert a timeline', async () => {
|
||||||
wrapper
|
let attachTimeline = noop;
|
||||||
.find('select[data-test-subj="prioritySelect"]')
|
useInsertTimelineMock.mockImplementation((value, onTimelineAttached) => {
|
||||||
.first()
|
attachTimeline = onTimelineAttached;
|
||||||
.simulate('change', {
|
|
||||||
target: { value: '2' },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click');
|
|
||||||
});
|
|
||||||
|
|
||||||
await waitFor(() =>
|
|
||||||
expect(postCase).toBeCalledWith({
|
|
||||||
...sampleData,
|
|
||||||
connector: {
|
|
||||||
id: 'jira-1',
|
|
||||||
name: 'Jira',
|
|
||||||
type: '.jira',
|
|
||||||
fields: { issueType: '10007', parent: null, priority: '2' },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`it should submit a resilient connector`, async () => {
|
const wrapper = mount(
|
||||||
useConnectorsMock.mockReturnValue({
|
<TestProviders>
|
||||||
...sampleConnectorData,
|
<Router history={mockHistory}>
|
||||||
connectors: connectorsMock,
|
<Create />
|
||||||
});
|
</Router>
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
const wrapper = mount(
|
act(() => {
|
||||||
<TestProviders>
|
attachTimeline('[title](url)');
|
||||||
<Router history={mockHistory}>
|
|
||||||
<Create />
|
|
||||||
</Router>
|
|
||||||
</TestProviders>
|
|
||||||
);
|
|
||||||
|
|
||||||
await fillForm(wrapper);
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(
|
|
||||||
wrapper.find(`[data-test-subj="connector-settings-resilient"]`).exists()
|
|
||||||
).toBeFalsy();
|
|
||||||
wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click');
|
|
||||||
wrapper.find(`button[data-test-subj="dropdown-connector-resilient-2"]`).simulate('click');
|
|
||||||
wrapper.update();
|
|
||||||
});
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
wrapper.update();
|
|
||||||
expect(
|
|
||||||
wrapper.find(`[data-test-subj="connector-settings-resilient"]`).exists()
|
|
||||||
).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
act(() => {
|
|
||||||
((wrapper.find(EuiComboBox).at(1).props() as unknown) as {
|
|
||||||
onChange: (a: EuiComboBoxOptionOption[]) => void;
|
|
||||||
}).onChange([{ value: '19', label: 'Denial of Service' }]);
|
|
||||||
});
|
|
||||||
|
|
||||||
act(() => {
|
|
||||||
wrapper
|
|
||||||
.find('select[data-test-subj="severitySelect"]')
|
|
||||||
.first()
|
|
||||||
.simulate('change', {
|
|
||||||
target: { value: '4' },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click');
|
|
||||||
});
|
|
||||||
|
|
||||||
await waitFor(() =>
|
|
||||||
expect(postCase).toBeCalledWith({
|
|
||||||
...sampleData,
|
|
||||||
connector: {
|
|
||||||
id: 'resilient-2',
|
|
||||||
name: 'My Connector 2',
|
|
||||||
type: '.resilient',
|
|
||||||
fields: { incidentTypes: ['19'], severityCode: '4' },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`it should submit a servicenow connector`, async () => {
|
await waitFor(() => {
|
||||||
useConnectorsMock.mockReturnValue({
|
expect(wrapper.find(`[data-test-subj="caseDescription"] textarea`).text()).toBe(
|
||||||
...sampleConnectorData,
|
'[title](url)'
|
||||||
connectors: connectorsMock,
|
|
||||||
});
|
|
||||||
|
|
||||||
const wrapper = mount(
|
|
||||||
<TestProviders>
|
|
||||||
<Router history={mockHistory}>
|
|
||||||
<Create />
|
|
||||||
</Router>
|
|
||||||
</TestProviders>
|
|
||||||
);
|
|
||||||
|
|
||||||
await fillForm(wrapper);
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(wrapper.find(`[data-test-subj="connector-settings-sn"]`).exists()).toBeFalsy();
|
|
||||||
wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click');
|
|
||||||
wrapper.find(`button[data-test-subj="dropdown-connector-servicenow-1"]`).simulate('click');
|
|
||||||
wrapper.update();
|
|
||||||
});
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
wrapper.update();
|
|
||||||
expect(wrapper.find(`[data-test-subj="connector-settings-sn"]`).exists()).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
['severitySelect', 'urgencySelect', 'impactSelect'].forEach((subj) => {
|
|
||||||
act(() => {
|
|
||||||
wrapper
|
|
||||||
.find(`select[data-test-subj="${subj}"]`)
|
|
||||||
.first()
|
|
||||||
.simulate('change', {
|
|
||||||
target: { value: '2' },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click');
|
|
||||||
});
|
|
||||||
|
|
||||||
await waitFor(() =>
|
|
||||||
expect(postCase).toBeCalledWith({
|
|
||||||
...sampleData,
|
|
||||||
connector: {
|
|
||||||
id: 'servicenow-1',
|
|
||||||
name: 'My Connector',
|
|
||||||
type: '.servicenow',
|
|
||||||
fields: { impact: '2', severity: '2', urgency: '2' },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
* 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 { CasePostRequest } from '../../../../../case/common/api';
|
||||||
|
import { ConnectorTypes } from '../../../../../case/common/api/connectors';
|
||||||
|
|
||||||
|
export const sampleTags = ['coke', 'pepsi'];
|
||||||
|
export const sampleData: CasePostRequest = {
|
||||||
|
description: 'what a great description',
|
||||||
|
tags: sampleTags,
|
||||||
|
title: 'what a cool title',
|
||||||
|
connector: {
|
||||||
|
fields: null,
|
||||||
|
id: 'none',
|
||||||
|
name: 'none',
|
||||||
|
type: ConnectorTypes.none,
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
syncAlerts: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sampleConnectorData = { loading: false, connectors: [] };
|
||||||
|
|
||||||
|
export const useGetIncidentTypesResponse = {
|
||||||
|
isLoading: false,
|
||||||
|
incidentTypes: [
|
||||||
|
{
|
||||||
|
id: 19,
|
||||||
|
name: 'Malware',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 21,
|
||||||
|
name: 'Denial of Service',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetSeverityResponse = {
|
||||||
|
isLoading: false,
|
||||||
|
severity: [
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: 'Low',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: 'Medium',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: 'High',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetIssueTypesResponse = {
|
||||||
|
isLoading: false,
|
||||||
|
issueTypes: [
|
||||||
|
{
|
||||||
|
id: '10006',
|
||||||
|
name: 'Task',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '10007',
|
||||||
|
name: 'Bug',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetFieldsByIssueTypeResponse = {
|
||||||
|
isLoading: false,
|
||||||
|
fields: {
|
||||||
|
summary: { allowedValues: [], defaultValue: {} },
|
||||||
|
labels: { allowedValues: [], defaultValue: {} },
|
||||||
|
description: { allowedValues: [], defaultValue: {} },
|
||||||
|
priority: {
|
||||||
|
allowedValues: [
|
||||||
|
{
|
||||||
|
name: 'Medium',
|
||||||
|
id: '3',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Low',
|
||||||
|
id: '2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
defaultValue: { name: 'Medium', id: '3' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
|
@ -10,13 +10,17 @@ import { act, waitFor } from '@testing-library/react';
|
||||||
|
|
||||||
import { useForm, Form } from '../../../shared_imports';
|
import { useForm, Form } from '../../../shared_imports';
|
||||||
import { SubmitCaseButton } from './submit_button';
|
import { SubmitCaseButton } from './submit_button';
|
||||||
|
import { schema, FormProps } from './schema';
|
||||||
|
|
||||||
describe('SubmitCaseButton', () => {
|
describe('SubmitCaseButton', () => {
|
||||||
const onSubmit = jest.fn();
|
const onSubmit = jest.fn();
|
||||||
|
|
||||||
const MockHookWrapperComponent: React.FC = ({ children }) => {
|
const MockHookWrapperComponent: React.FC = ({ children }) => {
|
||||||
const { form } = useForm<{ title: string }>({
|
const { form } = useForm<FormProps>({
|
||||||
defaultValue: { title: 'My title' },
|
defaultValue: { title: 'My title' },
|
||||||
|
schema: {
|
||||||
|
title: schema.title,
|
||||||
|
},
|
||||||
onSubmit,
|
onSubmit,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
* 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 React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import { waitFor } from '@testing-library/react';
|
||||||
|
|
||||||
|
import { useForm, Form, FormHook } from '../../../shared_imports';
|
||||||
|
import { SyncAlertsToggle } from './sync_alerts_toggle';
|
||||||
|
import { schema, FormProps } from './schema';
|
||||||
|
|
||||||
|
describe('SyncAlertsToggle', () => {
|
||||||
|
let globalForm: FormHook;
|
||||||
|
|
||||||
|
const MockHookWrapperComponent: React.FC = ({ children }) => {
|
||||||
|
const { form } = useForm<FormProps>({
|
||||||
|
defaultValue: { syncAlerts: true },
|
||||||
|
schema: {
|
||||||
|
syncAlerts: schema.syncAlerts,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
globalForm = form;
|
||||||
|
|
||||||
|
return <Form form={form}>{children}</Form>;
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it renders', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<MockHookWrapperComponent>
|
||||||
|
<SyncAlertsToggle isLoading={false} />
|
||||||
|
</MockHookWrapperComponent>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find(`[data-test-subj="caseSyncAlerts"]`).exists()).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it toggles the switch', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<MockHookWrapperComponent>
|
||||||
|
<SyncAlertsToggle isLoading={false} />
|
||||||
|
</MockHookWrapperComponent>
|
||||||
|
);
|
||||||
|
|
||||||
|
wrapper.find('[data-test-subj="caseSyncAlerts"] button').first().simulate('click');
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(globalForm.getFormData()).toEqual({ syncAlerts: false });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it shows the correct labels', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<MockHookWrapperComponent>
|
||||||
|
<SyncAlertsToggle isLoading={false} />
|
||||||
|
</MockHookWrapperComponent>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find(`[data-test-subj="caseSyncAlerts"] .euiSwitch__label`).first().text()).toBe(
|
||||||
|
'On'
|
||||||
|
);
|
||||||
|
|
||||||
|
wrapper.find('[data-test-subj="caseSyncAlerts"] button').first().simulate('click');
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(
|
||||||
|
wrapper.find(`[data-test-subj="caseSyncAlerts"] .euiSwitch__label`).first().text()
|
||||||
|
).toBe('Off');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -9,9 +9,10 @@ import { mount } from 'enzyme';
|
||||||
import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
|
import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
|
||||||
import { waitFor } from '@testing-library/react';
|
import { waitFor } from '@testing-library/react';
|
||||||
|
|
||||||
import { useForm, Form, FormHook, FIELD_TYPES } from '../../../shared_imports';
|
import { useForm, Form, FormHook } from '../../../shared_imports';
|
||||||
import { useGetTags } from '../../containers/use_get_tags';
|
import { useGetTags } from '../../containers/use_get_tags';
|
||||||
import { Tags } from './tags';
|
import { Tags } from './tags';
|
||||||
|
import { schema, FormProps } from './schema';
|
||||||
|
|
||||||
jest.mock('../../containers/use_get_tags');
|
jest.mock('../../containers/use_get_tags');
|
||||||
const useGetTagsMock = useGetTags as jest.Mock;
|
const useGetTagsMock = useGetTags as jest.Mock;
|
||||||
|
@ -20,10 +21,10 @@ describe('Tags', () => {
|
||||||
let globalForm: FormHook;
|
let globalForm: FormHook;
|
||||||
|
|
||||||
const MockHookWrapperComponent: React.FC = ({ children }) => {
|
const MockHookWrapperComponent: React.FC = ({ children }) => {
|
||||||
const { form } = useForm<{ tags: string[] }>({
|
const { form } = useForm<FormProps>({
|
||||||
defaultValue: { tags: [] },
|
defaultValue: { tags: [] },
|
||||||
schema: {
|
schema: {
|
||||||
tags: { type: FIELD_TYPES.COMBO_BOX },
|
tags: schema.tags,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -10,13 +10,17 @@ import { act } from '@testing-library/react';
|
||||||
|
|
||||||
import { useForm, Form, FormHook } from '../../../shared_imports';
|
import { useForm, Form, FormHook } from '../../../shared_imports';
|
||||||
import { Title } from './title';
|
import { Title } from './title';
|
||||||
|
import { schema, FormProps } from './schema';
|
||||||
|
|
||||||
describe('Title', () => {
|
describe('Title', () => {
|
||||||
let globalForm: FormHook;
|
let globalForm: FormHook;
|
||||||
|
|
||||||
const MockHookWrapperComponent: React.FC = ({ children }) => {
|
const MockHookWrapperComponent: React.FC = ({ children }) => {
|
||||||
const { form } = useForm<{ title: string }>({
|
const { form } = useForm<FormProps>({
|
||||||
defaultValue: { title: 'My title' },
|
defaultValue: { title: 'My title' },
|
||||||
|
schema: {
|
||||||
|
title: schema.title,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
globalForm = form;
|
globalForm = form;
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
* 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 React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
|
||||||
|
import { CaseStatuses } from '../../../../../case/common/api';
|
||||||
|
import { StatusActionButton } from './button';
|
||||||
|
|
||||||
|
describe('StatusActionButton', () => {
|
||||||
|
const onStatusChanged = jest.fn();
|
||||||
|
const defaultProps = {
|
||||||
|
status: CaseStatuses.open,
|
||||||
|
disabled: false,
|
||||||
|
isLoading: false,
|
||||||
|
onStatusChanged,
|
||||||
|
};
|
||||||
|
|
||||||
|
it('it renders', async () => {
|
||||||
|
const wrapper = mount(<StatusActionButton {...defaultProps} />);
|
||||||
|
|
||||||
|
expect(wrapper.find(`[data-test-subj="case-view-status-action-button"]`).exists()).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Button icons', () => {
|
||||||
|
it('it renders the correct button icon: status open', () => {
|
||||||
|
const wrapper = mount(<StatusActionButton {...defaultProps} />);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
wrapper.find(`[data-test-subj="case-view-status-action-button"]`).first().prop('iconType')
|
||||||
|
).toBe('folderExclamation');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it renders the correct button icon: status in-progress', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<StatusActionButton {...defaultProps} status={CaseStatuses['in-progress']} />
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
wrapper.find(`[data-test-subj="case-view-status-action-button"]`).first().prop('iconType')
|
||||||
|
).toBe('folderCheck');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it renders the correct button icon: status closed', () => {
|
||||||
|
const wrapper = mount(<StatusActionButton {...defaultProps} status={CaseStatuses.closed} />);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
wrapper.find(`[data-test-subj="case-view-status-action-button"]`).first().prop('iconType')
|
||||||
|
).toBe('folderCheck');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Status rotation', () => {
|
||||||
|
it('rotates correctly to in-progress when status is open', () => {
|
||||||
|
const wrapper = mount(<StatusActionButton {...defaultProps} />);
|
||||||
|
|
||||||
|
wrapper
|
||||||
|
.find(`button[data-test-subj="case-view-status-action-button"]`)
|
||||||
|
.first()
|
||||||
|
.simulate('click');
|
||||||
|
expect(onStatusChanged).toHaveBeenCalledWith('in-progress');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rotates correctly to closed when status is in-progress', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<StatusActionButton {...defaultProps} status={CaseStatuses['in-progress']} />
|
||||||
|
);
|
||||||
|
|
||||||
|
wrapper
|
||||||
|
.find(`button[data-test-subj="case-view-status-action-button"]`)
|
||||||
|
.first()
|
||||||
|
.simulate('click');
|
||||||
|
expect(onStatusChanged).toHaveBeenCalledWith('closed');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rotates correctly to open when status is closed', () => {
|
||||||
|
const wrapper = mount(<StatusActionButton {...defaultProps} status={CaseStatuses.closed} />);
|
||||||
|
|
||||||
|
wrapper
|
||||||
|
.find(`button[data-test-subj="case-view-status-action-button"]`)
|
||||||
|
.first()
|
||||||
|
.simulate('click');
|
||||||
|
expect(onStatusChanged).toHaveBeenCalledWith('open');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -38,7 +38,7 @@ const StatusActionButtonComponent: React.FC<Props> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EuiButton
|
<EuiButton
|
||||||
data-test-subj={'case-view-status-action-button'}
|
data-test-subj="case-view-status-action-button"
|
||||||
iconType={statuses[caseStatuses[nextStatusIndex]].button.icon}
|
iconType={statuses[caseStatuses[nextStatusIndex]].button.icon}
|
||||||
isDisabled={disabled}
|
isDisabled={disabled}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* 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 React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
|
||||||
|
import { CaseStatuses } from '../../../../../case/common/api';
|
||||||
|
import { Stats } from './stats';
|
||||||
|
|
||||||
|
describe('Stats', () => {
|
||||||
|
const defaultProps = {
|
||||||
|
caseStatus: CaseStatuses.open,
|
||||||
|
caseCount: 2,
|
||||||
|
isLoading: false,
|
||||||
|
dataTestSubj: 'test-stats',
|
||||||
|
};
|
||||||
|
it('it renders', async () => {
|
||||||
|
const wrapper = mount(<Stats {...defaultProps} />);
|
||||||
|
|
||||||
|
expect(wrapper.find(`[data-test-subj="test-stats"]`).exists()).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows the count', async () => {
|
||||||
|
const wrapper = mount(<Stats {...defaultProps} />);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
wrapper.find(`[data-test-subj="test-stats"] .euiDescriptionList__description`).first().text()
|
||||||
|
).toBe('2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows the loading spinner', async () => {
|
||||||
|
const wrapper = mount(<Stats {...defaultProps} isLoading={true} />);
|
||||||
|
|
||||||
|
expect(wrapper.find(`[data-test-subj="test-stats-loading-spinner"]`).exists()).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Status title', () => {
|
||||||
|
it('shows the correct title for status open', async () => {
|
||||||
|
const wrapper = mount(<Stats {...defaultProps} />);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
wrapper.find(`[data-test-subj="test-stats"] .euiDescriptionList__title`).first().text()
|
||||||
|
).toBe('Open cases');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows the correct title for status in-progress', async () => {
|
||||||
|
const wrapper = mount(<Stats {...defaultProps} caseStatus={CaseStatuses['in-progress']} />);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
wrapper.find(`[data-test-subj="test-stats"] .euiDescriptionList__title`).first().text()
|
||||||
|
).toBe('In progress cases');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows the correct title for status closed', async () => {
|
||||||
|
const wrapper = mount(<Stats {...defaultProps} caseStatus={CaseStatuses.closed} />);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
wrapper.find(`[data-test-subj="test-stats"] .euiDescriptionList__title`).first().text()
|
||||||
|
).toBe('Closed cases');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -21,10 +21,14 @@ const StatsComponent: React.FC<Props> = ({ caseCount, caseStatus, isLoading, dat
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
title: statuses[caseStatus].stats.title,
|
title: statuses[caseStatus].stats.title,
|
||||||
description: isLoading ? <EuiLoadingSpinner /> : caseCount ?? 'N/A',
|
description: isLoading ? (
|
||||||
|
<EuiLoadingSpinner data-test-subj={`${dataTestSubj}-loading-spinner`} />
|
||||||
|
) : (
|
||||||
|
caseCount ?? 'N/A'
|
||||||
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[caseCount, caseStatus, isLoading]
|
[caseCount, caseStatus, dataTestSubj, isLoading]
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<EuiDescriptionList data-test-subj={dataTestSubj} textStyle="reverse" listItems={statusStats} />
|
<EuiDescriptionList data-test-subj={dataTestSubj} textStyle="reverse" listItems={statusStats} />
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* 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 React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
|
||||||
|
import { CaseStatuses } from '../../../../../case/common/api';
|
||||||
|
import { Status } from './status';
|
||||||
|
|
||||||
|
describe('Stats', () => {
|
||||||
|
const onClick = jest.fn();
|
||||||
|
|
||||||
|
it('it renders', async () => {
|
||||||
|
const wrapper = mount(<Status type={CaseStatuses.open} withArrow={false} onClick={onClick} />);
|
||||||
|
|
||||||
|
expect(wrapper.find(`[data-test-subj="status-badge-open"]`).exists()).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
wrapper.find(`[data-test-subj="status-badge-open"] .euiBadge__iconButton`).exists()
|
||||||
|
).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it renders with arrow', async () => {
|
||||||
|
const wrapper = mount(<Status type={CaseStatuses.open} withArrow={true} onClick={onClick} />);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
wrapper.find(`[data-test-subj="status-badge-open"] .euiBadge__iconButton`).exists()
|
||||||
|
).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it calls onClick when pressing the badge', async () => {
|
||||||
|
const wrapper = mount(<Status type={CaseStatuses.open} withArrow={true} onClick={onClick} />);
|
||||||
|
|
||||||
|
wrapper.find(`[data-test-subj="status-badge-open"] .euiBadge__iconButton`).simulate('click');
|
||||||
|
expect(onClick).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Colors', () => {
|
||||||
|
it('shows the correct color when status is open', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<Status type={CaseStatuses.open} withArrow={false} onClick={onClick} />
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find(`[data-test-subj="status-badge-open"]`).first().prop('color')).toBe(
|
||||||
|
'primary'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows the correct color when status is in-progress', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<Status type={CaseStatuses['in-progress']} withArrow={false} onClick={onClick} />
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
wrapper.find(`[data-test-subj="status-badge-in-progress"]`).first().prop('color')
|
||||||
|
).toBe('warning');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows the correct color when status is closed', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<Status type={CaseStatuses.closed} withArrow={false} onClick={onClick} />
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find(`[data-test-subj="status-badge-closed"]`).first().prop('color')).toBe(
|
||||||
|
'default'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,190 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable react/display-name */
|
||||||
|
import React, { ReactNode } from 'react';
|
||||||
|
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import { TestProviders } from '../../../common/mock';
|
||||||
|
import { usePostComment } from '../../containers/use_post_comment';
|
||||||
|
import { AddToCaseAction } from './add_to_case_action';
|
||||||
|
|
||||||
|
jest.mock('../../containers/use_post_comment');
|
||||||
|
jest.mock('../../../common/lib/kibana', () => {
|
||||||
|
const originalModule = jest.requireActual('../../../common/lib/kibana');
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
useGetUserSavedObjectPermissions: jest.fn(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('../all_cases', () => {
|
||||||
|
return {
|
||||||
|
AllCases: ({ onRowClick }: { onRowClick: ({ id }: { id: string }) => void }) => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-test-subj="all-cases-modal-button"
|
||||||
|
onClick={() => onRowClick({ id: 'selected-case' })}
|
||||||
|
>
|
||||||
|
{'case-row'}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('../create/form_context', () => {
|
||||||
|
return {
|
||||||
|
FormContext: ({
|
||||||
|
children,
|
||||||
|
onSuccess,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
onSuccess: ({ id }: { id: string }) => void;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-test-subj="form-context-on-success"
|
||||||
|
onClick={() => onSuccess({ id: 'new-case' })}
|
||||||
|
>
|
||||||
|
{'submit'}
|
||||||
|
</button>
|
||||||
|
{children}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('../create/form', () => {
|
||||||
|
return {
|
||||||
|
CreateCaseForm: () => {
|
||||||
|
return <>{'form'}</>;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('../create/submit_button', () => {
|
||||||
|
return {
|
||||||
|
SubmitCaseButton: () => {
|
||||||
|
return <>{'Submit'}</>;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const usePostCommentMock = usePostComment as jest.Mock;
|
||||||
|
const postComment = jest.fn();
|
||||||
|
const defaultPostComment = {
|
||||||
|
isLoading: false,
|
||||||
|
isError: false,
|
||||||
|
postComment,
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('AddToCaseAction', () => {
|
||||||
|
const props = {
|
||||||
|
ecsRowData: {
|
||||||
|
_id: 'test-id',
|
||||||
|
_index: 'test-index',
|
||||||
|
},
|
||||||
|
disabled: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
usePostCommentMock.mockImplementation(() => defaultPostComment);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it renders', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<AddToCaseAction {...props} />
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find(`[data-test-subj="attach-alert-to-case-button"]`).exists()).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it opens the context menu', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<AddToCaseAction {...props} />
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
wrapper.find(`[data-test-subj="attach-alert-to-case-button"]`).first().simulate('click');
|
||||||
|
expect(wrapper.find(`[data-test-subj="add-new-case-item"]`).exists()).toBeTruthy();
|
||||||
|
expect(wrapper.find(`[data-test-subj="add-existing-case-menu-item"]`).exists()).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it opens the create case modal', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<AddToCaseAction {...props} />
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
wrapper.find(`[data-test-subj="attach-alert-to-case-button"]`).first().simulate('click');
|
||||||
|
wrapper.find(`[data-test-subj="add-new-case-item"]`).first().simulate('click');
|
||||||
|
|
||||||
|
expect(wrapper.find(`[data-test-subj="form-context-on-success"]`).exists()).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it attach the alert to case on case creation', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<AddToCaseAction {...props} />
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
wrapper.find(`[data-test-subj="attach-alert-to-case-button"]`).first().simulate('click');
|
||||||
|
wrapper.find(`[data-test-subj="add-new-case-item"]`).first().simulate('click');
|
||||||
|
|
||||||
|
wrapper.find(`[data-test-subj="form-context-on-success"]`).first().simulate('click');
|
||||||
|
|
||||||
|
expect(postComment.mock.calls[0][0]).toBe('new-case');
|
||||||
|
expect(postComment.mock.calls[0][1]).toEqual({
|
||||||
|
alertId: 'test-id',
|
||||||
|
index: 'test-index',
|
||||||
|
type: 'alert',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it opens the all cases modal', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<AddToCaseAction {...props} />
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
wrapper.find(`[data-test-subj="attach-alert-to-case-button"]`).first().simulate('click');
|
||||||
|
wrapper.find(`[data-test-subj="add-existing-case-menu-item"]`).first().simulate('click');
|
||||||
|
|
||||||
|
expect(wrapper.find(`[data-test-subj="all-cases-modal-button"]`).exists()).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it attach the alert to case after selecting a case', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<AddToCaseAction {...props} />
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
wrapper.find(`[data-test-subj="attach-alert-to-case-button"]`).first().simulate('click');
|
||||||
|
wrapper.find(`[data-test-subj="add-existing-case-menu-item"]`).first().simulate('click');
|
||||||
|
|
||||||
|
wrapper.find(`[data-test-subj="all-cases-modal-button"]`).first().simulate('click');
|
||||||
|
|
||||||
|
expect(postComment.mock.calls[0][0]).toBe('selected-case');
|
||||||
|
expect(postComment.mock.calls[0][1]).toEqual({
|
||||||
|
alertId: 'test-id',
|
||||||
|
index: 'test-index',
|
||||||
|
type: 'alert',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable react/display-name */
|
||||||
|
import React, { ReactNode } from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
|
||||||
|
import '../../../common/mock/match_media';
|
||||||
|
import { CreateCaseModal } from './create_case_modal';
|
||||||
|
import { TestProviders } from '../../../common/mock';
|
||||||
|
|
||||||
|
jest.mock('../create/form_context', () => {
|
||||||
|
return {
|
||||||
|
FormContext: ({
|
||||||
|
children,
|
||||||
|
onSuccess,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
onSuccess: ({ id }: { id: string }) => void;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-test-subj="form-context-on-success"
|
||||||
|
onClick={() => onSuccess({ id: 'case-id' })}
|
||||||
|
>
|
||||||
|
{'submit'}
|
||||||
|
</button>
|
||||||
|
{children}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('../create/form', () => {
|
||||||
|
return {
|
||||||
|
CreateCaseForm: () => {
|
||||||
|
return <>{'form'}</>;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('../create/submit_button', () => {
|
||||||
|
return {
|
||||||
|
SubmitCaseButton: () => {
|
||||||
|
return <>{'Submit'}</>;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const onCloseCaseModal = jest.fn();
|
||||||
|
const onSuccess = jest.fn();
|
||||||
|
const defaultProps = {
|
||||||
|
isModalOpen: true,
|
||||||
|
onCloseCaseModal,
|
||||||
|
onSuccess,
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('CreateCaseModal', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<CreateCaseModal {...defaultProps} />
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find(`[data-test-subj='all-cases-modal']`).exists()).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it does not render the modal isModalOpen=false ', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<CreateCaseModal {...defaultProps} isModalOpen={false} />
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find(`[data-test-subj='all-cases-modal']`).exists()).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Closing modal calls onCloseCaseModal', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<CreateCaseModal {...defaultProps} />
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
wrapper.find('.euiModal__closeIcon').first().simulate('click');
|
||||||
|
expect(onCloseCaseModal).toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('pass the correct props to FormContext component', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<CreateCaseModal {...defaultProps} />
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
const props = wrapper.find('FormContext').props();
|
||||||
|
expect(props).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
onSuccess,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('onSuccess called when creating a case', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<CreateCaseModal {...defaultProps} />
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
wrapper.find(`[data-test-subj='form-context-on-success']`).first().simulate('click');
|
||||||
|
expect(onSuccess).toHaveBeenCalledWith({ id: 'case-id' });
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,154 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable react/display-name */
|
||||||
|
import React, { ReactNode } from 'react';
|
||||||
|
import { renderHook, act } from '@testing-library/react-hooks';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
|
import { useKibana } from '../../../common/lib/kibana';
|
||||||
|
import '../../../common/mock/match_media';
|
||||||
|
import { useCreateCaseModal, UseCreateCaseModalProps, UseCreateCaseModalReturnedValues } from '.';
|
||||||
|
import { mockTimelineModel, TestProviders } from '../../../common/mock';
|
||||||
|
import { useDeepEqualSelector } from '../../../common/hooks/use_selector';
|
||||||
|
|
||||||
|
jest.mock('../../../common/lib/kibana');
|
||||||
|
jest.mock('../create/form_context', () => {
|
||||||
|
return {
|
||||||
|
FormContext: ({
|
||||||
|
children,
|
||||||
|
onSuccess,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
onSuccess: ({ id }: { id: string }) => void;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-test-subj="form-context-on-success"
|
||||||
|
onClick={() => onSuccess({ id: 'case-id' })}
|
||||||
|
>
|
||||||
|
{'Form submit'}
|
||||||
|
</button>
|
||||||
|
{children}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('../create/form', () => {
|
||||||
|
return {
|
||||||
|
CreateCaseForm: () => {
|
||||||
|
return <>{'form'}</>;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('../create/submit_button', () => {
|
||||||
|
return {
|
||||||
|
SubmitCaseButton: () => {
|
||||||
|
return <>{'Submit'}</>;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('../../../common/hooks/use_selector');
|
||||||
|
|
||||||
|
const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;
|
||||||
|
const onCaseCreated = jest.fn();
|
||||||
|
|
||||||
|
describe('useCreateCaseModal', () => {
|
||||||
|
let navigateToApp: jest.Mock;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
navigateToApp = jest.fn();
|
||||||
|
useKibanaMock().services.application.navigateToApp = navigateToApp;
|
||||||
|
(useDeepEqualSelector as jest.Mock).mockReturnValue(mockTimelineModel);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('init', async () => {
|
||||||
|
const { result } = renderHook<UseCreateCaseModalProps, UseCreateCaseModalReturnedValues>(
|
||||||
|
() => useCreateCaseModal({ onCaseCreated }),
|
||||||
|
{
|
||||||
|
wrapper: ({ children }) => <TestProviders>{children}</TestProviders>,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.current.isModalOpen).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('opens the modal', async () => {
|
||||||
|
const { result } = renderHook<UseCreateCaseModalProps, UseCreateCaseModalReturnedValues>(
|
||||||
|
() => useCreateCaseModal({ onCaseCreated }),
|
||||||
|
{
|
||||||
|
wrapper: ({ children }) => <TestProviders>{children}</TestProviders>,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
result.current.openModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.current.isModalOpen).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('closes the modal', async () => {
|
||||||
|
const { result } = renderHook<UseCreateCaseModalProps, UseCreateCaseModalReturnedValues>(
|
||||||
|
() => useCreateCaseModal({ onCaseCreated }),
|
||||||
|
{
|
||||||
|
wrapper: ({ children }) => <TestProviders>{children}</TestProviders>,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
result.current.openModal();
|
||||||
|
result.current.closeModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.current.isModalOpen).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns a memoized value', async () => {
|
||||||
|
const { result, rerender } = renderHook<
|
||||||
|
UseCreateCaseModalProps,
|
||||||
|
UseCreateCaseModalReturnedValues
|
||||||
|
>(() => useCreateCaseModal({ onCaseCreated }), {
|
||||||
|
wrapper: ({ children }) => <TestProviders>{children}</TestProviders>,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result1 = result.current;
|
||||||
|
act(() => rerender());
|
||||||
|
const result2 = result.current;
|
||||||
|
|
||||||
|
expect(Object.is(result1, result2)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('closes the modal when creating a case', async () => {
|
||||||
|
const { result } = renderHook<UseCreateCaseModalProps, UseCreateCaseModalReturnedValues>(
|
||||||
|
() => useCreateCaseModal({ onCaseCreated }),
|
||||||
|
{
|
||||||
|
wrapper: ({ children }) => <TestProviders>{children}</TestProviders>,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
result.current.openModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
const modal = result.current.modal;
|
||||||
|
render(<TestProviders>{modal}</TestProviders>);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
userEvent.click(screen.getByText('Form submit'));
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.current.isModalOpen).toBe(false);
|
||||||
|
expect(onCaseCreated).toHaveBeenCalledWith({ id: 'case-id' });
|
||||||
|
});
|
||||||
|
});
|
|
@ -8,17 +8,17 @@ import React, { useState, useCallback, useMemo } from 'react';
|
||||||
import { Case } from '../../containers/types';
|
import { Case } from '../../containers/types';
|
||||||
import { CreateCaseModal } from './create_case_modal';
|
import { CreateCaseModal } from './create_case_modal';
|
||||||
|
|
||||||
interface Props {
|
export interface UseCreateCaseModalProps {
|
||||||
onCaseCreated: (theCase: Case) => void;
|
onCaseCreated: (theCase: Case) => void;
|
||||||
}
|
}
|
||||||
export interface UseAllCasesModalReturnedValues {
|
export interface UseCreateCaseModalReturnedValues {
|
||||||
modal: JSX.Element;
|
modal: JSX.Element;
|
||||||
isModalOpen: boolean;
|
isModalOpen: boolean;
|
||||||
closeModal: () => void;
|
closeModal: () => void;
|
||||||
openModal: () => void;
|
openModal: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useCreateCaseModal = ({ onCaseCreated }: Props) => {
|
export const useCreateCaseModal = ({ onCaseCreated }: UseCreateCaseModalProps) => {
|
||||||
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
|
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
|
||||||
const closeModal = useCallback(() => setIsModalOpen(false), []);
|
const closeModal = useCallback(() => setIsModalOpen(false), []);
|
||||||
const openModal = useCallback(() => setIsModalOpen(true), []);
|
const openModal = useCallback(() => setIsModalOpen(true), []);
|
||||||
|
|
|
@ -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 { renderHook } from '@testing-library/react-hooks';
|
||||||
|
|
||||||
|
import { mockTimelineModel } from '../../../common/mock';
|
||||||
|
import { useFormatUrl } from '../../../common/components/link_to';
|
||||||
|
import { SecurityPageName } from '../../../app/types';
|
||||||
|
import { useInsertTimeline } from '.';
|
||||||
|
|
||||||
|
const mockDispatch = jest.fn();
|
||||||
|
|
||||||
|
jest.mock('react-redux', () => {
|
||||||
|
const original = jest.requireActual('react-redux');
|
||||||
|
return {
|
||||||
|
...original,
|
||||||
|
useDispatch: () => mockDispatch,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('../../../common/components/link_to', () => {
|
||||||
|
const originalModule = jest.requireActual('../../../common/components/link_to');
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
getTimelineTabsUrl: jest.fn(),
|
||||||
|
useFormatUrl: jest.fn().mockReturnValue({
|
||||||
|
formatUrl: jest.fn().mockImplementation((path: string) => path),
|
||||||
|
search: '',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('../../../common/hooks/use_selector', () => ({
|
||||||
|
useShallowEqualSelector: jest.fn().mockReturnValue({
|
||||||
|
timelineTitle: mockTimelineModel.title,
|
||||||
|
timelineSavedObjectId: mockTimelineModel.savedObjectId,
|
||||||
|
graphEventId: mockTimelineModel.graphEventId,
|
||||||
|
timelineId: mockTimelineModel.id,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('useInsertTimeline', () => {
|
||||||
|
const onChange = jest.fn();
|
||||||
|
const { formatUrl } = useFormatUrl(SecurityPageName.timelines);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('init', async () => {
|
||||||
|
renderHook(() => useInsertTimeline('', onChange));
|
||||||
|
|
||||||
|
expect(mockDispatch).toHaveBeenNthCalledWith(1, {
|
||||||
|
payload: { id: 'ef579e40-jibber-jabber', show: false },
|
||||||
|
type: 'x-pack/security_solution/local/timeline/SHOW_TIMELINE',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockDispatch).toHaveBeenNthCalledWith(2, {
|
||||||
|
payload: null,
|
||||||
|
type: 'x-pack/security_solution/local/timeline/SET_INSERT_TIMELINE',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(onChange).toHaveBeenCalledWith(
|
||||||
|
`[Test rule](?timeline=(id:'ef579e40-jibber-jabber',isOpen:!t))`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it appends the value if is not empty', async () => {
|
||||||
|
renderHook(() => useInsertTimeline('New value', onChange));
|
||||||
|
|
||||||
|
expect(onChange).toHaveBeenCalledWith(
|
||||||
|
`New value [Test rule](?timeline=(id:'ef579e40-jibber-jabber',isOpen:!t))`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls formatUrl with correct options', async () => {
|
||||||
|
renderHook(() => useInsertTimeline('', onChange));
|
||||||
|
|
||||||
|
expect(formatUrl).toHaveBeenCalledWith(`?timeline=(id:'ef579e40-jibber-jabber',isOpen:!t)`, {
|
||||||
|
absolute: true,
|
||||||
|
skipSearch: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -14,7 +14,7 @@ import { timelineSelectors, timelineActions } from '../../../timelines/store/tim
|
||||||
import { SecurityPageName } from '../../../app/types';
|
import { SecurityPageName } from '../../../app/types';
|
||||||
import { setInsertTimeline } from '../../../timelines/store/timeline/actions';
|
import { setInsertTimeline } from '../../../timelines/store/timeline/actions';
|
||||||
|
|
||||||
interface UseInsertTimelineReturn {
|
export interface UseInsertTimelineReturn {
|
||||||
handleOnTimelineChange: (title: string, id: string | null, graphEventId?: string) => void;
|
handleOnTimelineChange: (title: string, id: string | null, graphEventId?: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
|
||||||
import { CaseStatuses } from '../../../../../case/common/api';
|
import { CaseStatuses } from '../../../../../case/common/api';
|
||||||
import { basicPush, getUserAction } from '../../containers/mock';
|
import { basicPush, getUserAction } from '../../containers/mock';
|
||||||
import { getLabelTitle, getPushedServiceLabelTitle, getConnectorLabelTitle } from './helpers';
|
import { getLabelTitle, getPushedServiceLabelTitle, getConnectorLabelTitle } from './helpers';
|
||||||
import { mount } from 'enzyme';
|
|
||||||
import { connectorsMock } from '../../containers/configure/mock';
|
import { connectorsMock } from '../../containers/configure/mock';
|
||||||
import * as i18n from './translations';
|
import * as i18n from './translations';
|
||||||
|
|
||||||
|
@ -56,24 +56,52 @@ describe('User action tree helpers', () => {
|
||||||
expect(result).toEqual(`${i18n.EDITED_FIELD} ${i18n.DESCRIPTION.toLowerCase()}`);
|
expect(result).toEqual(`${i18n.EDITED_FIELD} ${i18n.DESCRIPTION.toLowerCase()}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it.skip('label title generated for update status to open', () => {
|
it('label title generated for update status to open', () => {
|
||||||
const action = { ...getUserAction(['status'], 'update'), newValue: CaseStatuses.open };
|
const action = { ...getUserAction(['status'], 'update'), newValue: CaseStatuses.open };
|
||||||
const result: string | JSX.Element = getLabelTitle({
|
const result: string | JSX.Element = getLabelTitle({
|
||||||
action,
|
action,
|
||||||
field: 'status',
|
field: 'status',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toEqual(`${i18n.REOPEN_CASE.toLowerCase()} ${i18n.CASE}`);
|
const wrapper = mount(<>{result}</>);
|
||||||
|
expect(wrapper.find(`[data-test-subj="status-badge-open"]`).first().text()).toEqual('Open');
|
||||||
});
|
});
|
||||||
|
|
||||||
it.skip('label title generated for update status to closed', () => {
|
it('label title generated for update status to in-progress', () => {
|
||||||
|
const action = {
|
||||||
|
...getUserAction(['status'], 'update'),
|
||||||
|
newValue: CaseStatuses['in-progress'],
|
||||||
|
};
|
||||||
|
const result: string | JSX.Element = getLabelTitle({
|
||||||
|
action,
|
||||||
|
field: 'status',
|
||||||
|
});
|
||||||
|
|
||||||
|
const wrapper = mount(<>{result}</>);
|
||||||
|
expect(wrapper.find(`[data-test-subj="status-badge-in-progress"]`).first().text()).toEqual(
|
||||||
|
'In progress'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('label title generated for update status to closed', () => {
|
||||||
const action = { ...getUserAction(['status'], 'update'), newValue: CaseStatuses.closed };
|
const action = { ...getUserAction(['status'], 'update'), newValue: CaseStatuses.closed };
|
||||||
const result: string | JSX.Element = getLabelTitle({
|
const result: string | JSX.Element = getLabelTitle({
|
||||||
action,
|
action,
|
||||||
field: 'status',
|
field: 'status',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toEqual(`${i18n.CLOSE_CASE.toLowerCase()} ${i18n.CASE}`);
|
const wrapper = mount(<>{result}</>);
|
||||||
|
expect(wrapper.find(`[data-test-subj="status-badge-closed"]`).first().text()).toEqual('Closed');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('label title is empty when status is not valid', () => {
|
||||||
|
const action = { ...getUserAction(['status'], 'update'), newValue: CaseStatuses.closed };
|
||||||
|
const result: string | JSX.Element = getLabelTitle({
|
||||||
|
action: { ...action, newValue: 'not-exist' },
|
||||||
|
field: 'status',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toEqual('');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('label title generated for update comment', () => {
|
it('label title generated for update comment', () => {
|
||||||
|
|
|
@ -31,9 +31,13 @@ interface LabelTitle {
|
||||||
field: string;
|
field: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStatusTitle = (status: CaseStatuses) => {
|
const getStatusTitle = (id: string, status: CaseStatuses) => {
|
||||||
return (
|
return (
|
||||||
<EuiFlexGroup gutterSize="s" alignItems={'center'}>
|
<EuiFlexGroup
|
||||||
|
gutterSize="s"
|
||||||
|
alignItems={'center'}
|
||||||
|
data-test-subj={`${id}-user-action-status-title`}
|
||||||
|
>
|
||||||
<EuiFlexItem grow={false}>{i18n.MARKED_CASE_AS}</EuiFlexItem>
|
<EuiFlexItem grow={false}>{i18n.MARKED_CASE_AS}</EuiFlexItem>
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<Status type={status} />
|
<Status type={status} />
|
||||||
|
@ -42,6 +46,9 @@ const getStatusTitle = (status: CaseStatuses) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isStatusValid = (status: string): status is CaseStatuses =>
|
||||||
|
Object.prototype.hasOwnProperty.call(statuses, status);
|
||||||
|
|
||||||
export const getLabelTitle = ({ action, field }: LabelTitle) => {
|
export const getLabelTitle = ({ action, field }: LabelTitle) => {
|
||||||
if (field === 'tags') {
|
if (field === 'tags') {
|
||||||
return getTagsLabelTitle(action);
|
return getTagsLabelTitle(action);
|
||||||
|
@ -52,12 +59,12 @@ export const getLabelTitle = ({ action, field }: LabelTitle) => {
|
||||||
} else if (field === 'description' && action.action === 'update') {
|
} else if (field === 'description' && action.action === 'update') {
|
||||||
return `${i18n.EDITED_FIELD} ${i18n.DESCRIPTION.toLowerCase()}`;
|
return `${i18n.EDITED_FIELD} ${i18n.DESCRIPTION.toLowerCase()}`;
|
||||||
} else if (field === 'status' && action.action === 'update') {
|
} else if (field === 'status' && action.action === 'update') {
|
||||||
if (!Object.prototype.hasOwnProperty.call(statuses, action.newValue ?? '')) {
|
const status = action.newValue ?? '';
|
||||||
return '';
|
if (isStatusValid(status)) {
|
||||||
|
return getStatusTitle(action.actionId, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The above check ensures that the newValue is of type CaseStatuses.
|
return '';
|
||||||
return getStatusTitle(action.newValue as CaseStatuses);
|
|
||||||
} else if (field === 'comment' && action.action === 'update') {
|
} else if (field === 'comment' && action.action === 'update') {
|
||||||
return `${i18n.EDITED_FIELD} ${i18n.COMMENT.toLowerCase()}`;
|
return `${i18n.EDITED_FIELD} ${i18n.COMMENT.toLowerCase()}`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
/*
|
||||||
|
* 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 React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
|
||||||
|
import { TestProviders } from '../../../common/mock';
|
||||||
|
import { useKibana } from '../../../common/lib/kibana';
|
||||||
|
import { AlertCommentEvent } from './user_action_alert_comment_event';
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
alert: {
|
||||||
|
_id: 'alert-id-1',
|
||||||
|
_index: 'alert-index-1',
|
||||||
|
'@timestamp': '2021-01-07T13:58:31.487Z',
|
||||||
|
rule: {
|
||||||
|
id: 'rule-id-1',
|
||||||
|
name: 'Awesome rule',
|
||||||
|
from: '2021-01-07T13:58:31.487Z',
|
||||||
|
to: '2021-01-07T14:58:31.487Z',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
jest.mock('../../../common/lib/kibana');
|
||||||
|
const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;
|
||||||
|
|
||||||
|
describe('UserActionAvatar ', () => {
|
||||||
|
let navigateToApp: jest.Mock;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
navigateToApp = jest.fn();
|
||||||
|
useKibanaMock().services.application.navigateToApp = navigateToApp;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it renders', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<AlertCommentEvent {...props} />
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
wrapper.find(`[data-test-subj="alert-rule-link-alert-id-1"]`).first().exists()
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(wrapper.text()).toBe('added an alert from Awesome rule');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does NOT render the link when the alert is undefined', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<AlertCommentEvent alert={undefined} />
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
wrapper.find(`[data-test-subj="alert-rule-link-alert-id-1"]`).first().exists()
|
||||||
|
).toBeFalsy();
|
||||||
|
|
||||||
|
expect(wrapper.text()).toBe('added an alert');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does NOT render the link when the rule is undefined', async () => {
|
||||||
|
const alert = {
|
||||||
|
_id: 'alert-id-1',
|
||||||
|
_index: 'alert-index-1',
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
{/* @ts-expect-error*/}
|
||||||
|
<AlertCommentEvent alert={alert} />
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
wrapper.find(`[data-test-subj="alert-rule-link-alert-id-1"]`).first().exists()
|
||||||
|
).toBeFalsy();
|
||||||
|
|
||||||
|
expect(wrapper.text()).toBe('added an alert');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('navigate to app on link click', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<TestProviders>
|
||||||
|
<AlertCommentEvent {...props} />
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
|
wrapper.find(`[data-test-subj="alert-rule-link-alert-id-1"]`).first().simulate('click');
|
||||||
|
expect(navigateToApp).toHaveBeenCalledWith('securitySolution:detections', {
|
||||||
|
path: '/rules/id/rule-id-1',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -37,7 +37,9 @@ const AlertCommentEventComponent: React.FC<Props> = ({ alert }) => {
|
||||||
return ruleId != null && ruleName != null ? (
|
return ruleId != null && ruleName != null ? (
|
||||||
<>
|
<>
|
||||||
{`${i18n.ALERT_COMMENT_LABEL_TITLE} `}
|
{`${i18n.ALERT_COMMENT_LABEL_TITLE} `}
|
||||||
<EuiLink onClick={onLinkClick}>{ruleName}</EuiLink>
|
<EuiLink onClick={onLinkClick} data-test-subj={`alert-rule-link-${alert?._id ?? 'deleted'}`}>
|
||||||
|
{ruleName}
|
||||||
|
</EuiLink>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>{i18n.ALERT_RULE_DELETED_COMMENT_LABEL}</>
|
<>{i18n.ALERT_RULE_DELETED_COMMENT_LABEL}</>
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* 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 React from 'react';
|
||||||
|
import { mount, ReactWrapper } from 'enzyme';
|
||||||
|
import { UserActionShowAlert } from './user_action_show_alert';
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
id: 'action-id',
|
||||||
|
alert: {
|
||||||
|
_id: 'alert-id',
|
||||||
|
_index: 'alert-index',
|
||||||
|
'@timestamp': '2021-01-07T13:58:31.487Z',
|
||||||
|
rule: {
|
||||||
|
id: 'rule-id',
|
||||||
|
name: 'Awesome Rule',
|
||||||
|
from: '2021-01-07T13:58:31.487Z',
|
||||||
|
to: '2021-01-07T14:58:31.487Z',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('UserActionShowAlert ', () => {
|
||||||
|
let wrapper: ReactWrapper;
|
||||||
|
const onShowAlertDetails = jest.fn();
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
wrapper = mount(<UserActionShowAlert {...props} onShowAlertDetails={onShowAlertDetails} />);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it renders', async () => {
|
||||||
|
expect(
|
||||||
|
wrapper.find('[data-test-subj="comment-action-show-alert-action-id"]').first().exists()
|
||||||
|
).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it calls onClick', async () => {
|
||||||
|
wrapper.find('button[data-test-subj="comment-action-show-alert-action-id"]').simulate('click');
|
||||||
|
expect(onShowAlertDetails).toHaveBeenCalledWith('alert-id', 'alert-index');
|
||||||
|
});
|
||||||
|
});
|
|
@ -54,6 +54,20 @@ export const basicComment: Comment = {
|
||||||
version: 'WzQ3LDFc',
|
version: 'WzQ3LDFc',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const alertComment: Comment = {
|
||||||
|
alertId: 'alert-id-1',
|
||||||
|
index: 'alert-index-1',
|
||||||
|
type: CommentType.alert,
|
||||||
|
id: 'alert-comment-id',
|
||||||
|
createdAt: basicCreatedAt,
|
||||||
|
createdBy: elasticUser,
|
||||||
|
pushedAt: null,
|
||||||
|
pushedBy: null,
|
||||||
|
updatedAt: null,
|
||||||
|
updatedBy: null,
|
||||||
|
version: 'WzQ3LDFc',
|
||||||
|
};
|
||||||
|
|
||||||
export const basicCase: Case = {
|
export const basicCase: Case = {
|
||||||
closedAt: null,
|
closedAt: null,
|
||||||
closedBy: null,
|
closedBy: null,
|
||||||
|
@ -311,6 +325,15 @@ export const getUserAction = (af: UserActionField, a: UserAction) => ({
|
||||||
: basicAction.newValue,
|
: basicAction.newValue,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const getAlertUserAction = () => ({
|
||||||
|
...basicAction,
|
||||||
|
actionId: 'alert-action-id',
|
||||||
|
actionField: ['comment'],
|
||||||
|
action: 'create',
|
||||||
|
commentId: 'alert-comment-id',
|
||||||
|
newValue: '{"type":"alert","alertId":"alert-id-1","index":"index-id-1"}',
|
||||||
|
});
|
||||||
|
|
||||||
export const caseUserActions: CaseUserActions[] = [
|
export const caseUserActions: CaseUserActions[] = [
|
||||||
getUserAction(['description'], 'create'),
|
getUserAction(['description'], 'create'),
|
||||||
getUserAction(['comment'], 'create'),
|
getUserAction(['comment'], 'create'),
|
||||||
|
|
|
@ -10,7 +10,7 @@ export { getDetectionEngineUrl } from '../redirect_to_detection_engine';
|
||||||
export { getAppOverviewUrl } from '../redirect_to_overview';
|
export { getAppOverviewUrl } from '../redirect_to_overview';
|
||||||
export { getHostDetailsUrl, getHostsUrl } from '../redirect_to_hosts';
|
export { getHostDetailsUrl, getHostsUrl } from '../redirect_to_hosts';
|
||||||
export { getNetworkUrl, getNetworkDetailsUrl } from '../redirect_to_network';
|
export { getNetworkUrl, getNetworkDetailsUrl } from '../redirect_to_network';
|
||||||
export { getTimelinesUrl, getTimelineTabsUrl } from '../redirect_to_timelines';
|
export { getTimelinesUrl, getTimelineTabsUrl, getTimelineUrl } from '../redirect_to_timelines';
|
||||||
export {
|
export {
|
||||||
getCaseDetailsUrl,
|
getCaseDetailsUrl,
|
||||||
getCaseUrl,
|
getCaseUrl,
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable react/display-name */
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
@ -11,11 +13,19 @@ import { DEFAULT_ACTIONS_COLUMN_WIDTH } from '../constants';
|
||||||
import * as i18n from '../translations';
|
import * as i18n from '../translations';
|
||||||
|
|
||||||
import { EventColumnView } from './event_column_view';
|
import { EventColumnView } from './event_column_view';
|
||||||
import { TimelineTabs, TimelineType } from '../../../../../../common/types/timeline';
|
import { TimelineTabs, TimelineType, TimelineId } from '../../../../../../common/types/timeline';
|
||||||
import { useShallowEqualSelector } from '../../../../../common/hooks/use_selector';
|
import { useShallowEqualSelector } from '../../../../../common/hooks/use_selector';
|
||||||
|
|
||||||
jest.mock('../../../../../common/hooks/use_selector');
|
jest.mock('../../../../../common/hooks/use_selector');
|
||||||
|
|
||||||
|
jest.mock('../../../../../cases/components/timeline_actions/add_to_case_action', () => {
|
||||||
|
return {
|
||||||
|
AddToCaseAction: () => {
|
||||||
|
return <div data-test-subj="add-to-case-action">{'Add to case'}</div>;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
describe('EventColumnView', () => {
|
describe('EventColumnView', () => {
|
||||||
(useShallowEqualSelector as jest.Mock).mockReturnValue(TimelineType.default);
|
(useShallowEqualSelector as jest.Mock).mockReturnValue(TimelineType.default);
|
||||||
|
|
||||||
|
@ -49,7 +59,7 @@ describe('EventColumnView', () => {
|
||||||
showCheckboxes: false,
|
showCheckboxes: false,
|
||||||
showNotes: false,
|
showNotes: false,
|
||||||
tabType: TimelineTabs.query,
|
tabType: TimelineTabs.query,
|
||||||
timelineId: 'timeline-test',
|
timelineId: TimelineId.active,
|
||||||
toggleShowNotes: jest.fn(),
|
toggleShowNotes: jest.fn(),
|
||||||
updateNote: jest.fn(),
|
updateNote: jest.fn(),
|
||||||
isEventPinned: false,
|
isEventPinned: false,
|
||||||
|
@ -107,4 +117,39 @@ describe('EventColumnView', () => {
|
||||||
|
|
||||||
expect(props.onPinEvent).toHaveBeenCalled();
|
expect(props.onPinEvent).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('it render AddToCaseAction if timelineId === TimelineId.detectionsPage', () => {
|
||||||
|
const wrapper = mount(<EventColumnView {...props} timelineId={TimelineId.detectionsPage} />, {
|
||||||
|
wrappingComponent: TestProviders,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.find('[data-test-subj="add-to-case-action"]').exists()).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it render AddToCaseAction if timelineId === TimelineId.detectionsRulesDetailsPage', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<EventColumnView {...props} timelineId={TimelineId.detectionsRulesDetailsPage} />,
|
||||||
|
{
|
||||||
|
wrappingComponent: TestProviders,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find('[data-test-subj="add-to-case-action"]').exists()).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it render AddToCaseAction if timelineId === TimelineId.active', () => {
|
||||||
|
const wrapper = mount(<EventColumnView {...props} timelineId={TimelineId.active} />, {
|
||||||
|
wrappingComponent: TestProviders,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.find('[data-test-subj="add-to-case-action"]').exists()).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it does NOT render AddToCaseAction when timelineId is not in the allowed list', () => {
|
||||||
|
const wrapper = mount(<EventColumnView {...props} timelineId="timeline-test" />, {
|
||||||
|
wrappingComponent: TestProviders,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.find('[data-test-subj="add-to-case-action"]').exists()).toBeFalsy();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,6 +9,7 @@ import expect from '@kbn/expect';
|
||||||
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
|
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
|
||||||
|
|
||||||
import { CASES_URL } from '../../../../../../plugins/case/common/constants';
|
import { CASES_URL } from '../../../../../../plugins/case/common/constants';
|
||||||
|
import { DETECTION_ENGINE_QUERY_SIGNALS_URL } from '../../../../../../plugins/security_solution/common/constants';
|
||||||
import { CommentType } from '../../../../../../plugins/case/common/api';
|
import { CommentType } from '../../../../../../plugins/case/common/api';
|
||||||
import {
|
import {
|
||||||
defaultUser,
|
defaultUser,
|
||||||
|
@ -17,10 +18,22 @@ import {
|
||||||
postCommentAlertReq,
|
postCommentAlertReq,
|
||||||
} from '../../../../common/lib/mock';
|
} from '../../../../common/lib/mock';
|
||||||
import { deleteCases, deleteCasesUserActions, deleteComments } from '../../../../common/lib/utils';
|
import { deleteCases, deleteCasesUserActions, deleteComments } from '../../../../common/lib/utils';
|
||||||
|
import {
|
||||||
|
createSignalsIndex,
|
||||||
|
deleteSignalsIndex,
|
||||||
|
deleteAllAlerts,
|
||||||
|
getRuleForSignalTesting,
|
||||||
|
waitForRuleSuccessOrStatus,
|
||||||
|
waitForSignalsToBePresent,
|
||||||
|
getSignalsByIds,
|
||||||
|
createRule,
|
||||||
|
getQuerySignalIds,
|
||||||
|
} from '../../../../../detection_engine_api_integration/utils';
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-default-export
|
// eslint-disable-next-line import/no-default-export
|
||||||
export default ({ getService }: FtrProviderContext): void => {
|
export default ({ getService }: FtrProviderContext): void => {
|
||||||
const supertest = getService('supertest');
|
const supertest = getService('supertest');
|
||||||
|
const esArchiver = getService('esArchiver');
|
||||||
const es = getService('es');
|
const es = getService('es');
|
||||||
|
|
||||||
describe('post_comment', () => {
|
describe('post_comment', () => {
|
||||||
|
@ -166,5 +179,146 @@ export default ({ getService }: FtrProviderContext): void => {
|
||||||
})
|
})
|
||||||
.expect(400);
|
.expect(400);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('unhappy path - 400s when adding an alert to a closed case', async () => {
|
||||||
|
const { body: postedCase } = await supertest
|
||||||
|
.post(CASES_URL)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send(postCaseReq)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
await supertest
|
||||||
|
.patch(CASES_URL)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send({
|
||||||
|
cases: [
|
||||||
|
{
|
||||||
|
id: postedCase.id,
|
||||||
|
version: postedCase.version,
|
||||||
|
status: 'closed',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
await supertest
|
||||||
|
.post(`${CASES_URL}/${postedCase.id}/comments`)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send(postCommentAlertReq)
|
||||||
|
.expect(400);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('alerts', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await esArchiver.load('auditbeat/hosts');
|
||||||
|
await createSignalsIndex(supertest);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await deleteSignalsIndex(supertest);
|
||||||
|
await deleteAllAlerts(supertest);
|
||||||
|
await esArchiver.unload('auditbeat/hosts');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should change the status of the alert if sync alert is on', async () => {
|
||||||
|
const rule = getRuleForSignalTesting(['auditbeat-*']);
|
||||||
|
|
||||||
|
const { body: postedCase } = await supertest
|
||||||
|
.post(CASES_URL)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send(postCaseReq)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
await supertest
|
||||||
|
.patch(CASES_URL)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send({
|
||||||
|
cases: [
|
||||||
|
{
|
||||||
|
id: postedCase.id,
|
||||||
|
version: postedCase.version,
|
||||||
|
status: 'in-progress',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const { id } = await createRule(supertest, rule);
|
||||||
|
await waitForRuleSuccessOrStatus(supertest, id);
|
||||||
|
await waitForSignalsToBePresent(supertest, 1, [id]);
|
||||||
|
const signals = await getSignalsByIds(supertest, [id]);
|
||||||
|
|
||||||
|
const alert = signals.hits.hits[0];
|
||||||
|
expect(alert._source.signal.status).eql('open');
|
||||||
|
|
||||||
|
await supertest
|
||||||
|
.post(`${CASES_URL}/${postedCase.id}/comments`)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send({
|
||||||
|
alertId: alert._id,
|
||||||
|
index: alert._index,
|
||||||
|
type: CommentType.alert,
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const { body: updatedAlert } = await supertest
|
||||||
|
.post(DETECTION_ENGINE_QUERY_SIGNALS_URL)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send(getQuerySignalIds([alert._id]))
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(updatedAlert.hits.hits[0]._source.signal.status).eql('in-progress');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should NOT change the status of the alert if sync alert is off', async () => {
|
||||||
|
const rule = getRuleForSignalTesting(['auditbeat-*']);
|
||||||
|
|
||||||
|
const { body: postedCase } = await supertest
|
||||||
|
.post(CASES_URL)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send({ ...postCaseReq, settings: { syncAlerts: false } })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
await supertest
|
||||||
|
.patch(CASES_URL)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send({
|
||||||
|
cases: [
|
||||||
|
{
|
||||||
|
id: postedCase.id,
|
||||||
|
version: postedCase.version,
|
||||||
|
status: 'in-progress',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const { id } = await createRule(supertest, rule);
|
||||||
|
await waitForRuleSuccessOrStatus(supertest, id);
|
||||||
|
await waitForSignalsToBePresent(supertest, 1, [id]);
|
||||||
|
const signals = await getSignalsByIds(supertest, [id]);
|
||||||
|
|
||||||
|
const alert = signals.hits.hits[0];
|
||||||
|
expect(alert._source.signal.status).eql('open');
|
||||||
|
|
||||||
|
await supertest
|
||||||
|
.post(`${CASES_URL}/${postedCase.id}/comments`)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send({
|
||||||
|
alertId: alert._id,
|
||||||
|
index: alert._index,
|
||||||
|
type: CommentType.alert,
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const { body: updatedAlert } = await supertest
|
||||||
|
.post(DETECTION_ENGINE_QUERY_SIGNALS_URL)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send(getQuerySignalIds([alert._id]))
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(updatedAlert.hits.hits[0]._source.signal.status).eql('open');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,6 +8,8 @@ import expect from '@kbn/expect';
|
||||||
import { FtrProviderContext } from '../../../common/ftr_provider_context';
|
import { FtrProviderContext } from '../../../common/ftr_provider_context';
|
||||||
|
|
||||||
import { CASES_URL } from '../../../../../plugins/case/common/constants';
|
import { CASES_URL } from '../../../../../plugins/case/common/constants';
|
||||||
|
import { DETECTION_ENGINE_QUERY_SIGNALS_URL } from '../../../../../plugins/security_solution/common/constants';
|
||||||
|
import { CommentType } from '../../../../../plugins/case/common/api';
|
||||||
import {
|
import {
|
||||||
defaultUser,
|
defaultUser,
|
||||||
postCaseReq,
|
postCaseReq,
|
||||||
|
@ -15,10 +17,22 @@ import {
|
||||||
removeServerGeneratedPropertiesFromCase,
|
removeServerGeneratedPropertiesFromCase,
|
||||||
} from '../../../common/lib/mock';
|
} from '../../../common/lib/mock';
|
||||||
import { deleteCases, deleteCasesUserActions } from '../../../common/lib/utils';
|
import { deleteCases, deleteCasesUserActions } from '../../../common/lib/utils';
|
||||||
|
import {
|
||||||
|
createSignalsIndex,
|
||||||
|
deleteSignalsIndex,
|
||||||
|
deleteAllAlerts,
|
||||||
|
getRuleForSignalTesting,
|
||||||
|
waitForRuleSuccessOrStatus,
|
||||||
|
waitForSignalsToBePresent,
|
||||||
|
getSignalsByIds,
|
||||||
|
createRule,
|
||||||
|
getQuerySignalIds,
|
||||||
|
} from '../../../../detection_engine_api_integration/utils';
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-default-export
|
// eslint-disable-next-line import/no-default-export
|
||||||
export default ({ getService }: FtrProviderContext): void => {
|
export default ({ getService }: FtrProviderContext): void => {
|
||||||
const supertest = getService('supertest');
|
const supertest = getService('supertest');
|
||||||
|
const esArchiver = getService('esArchiver');
|
||||||
const es = getService('es');
|
const es = getService('es');
|
||||||
|
|
||||||
describe('patch_cases', () => {
|
describe('patch_cases', () => {
|
||||||
|
@ -248,5 +262,250 @@ export default ({ getService }: FtrProviderContext): void => {
|
||||||
})
|
})
|
||||||
.expect(409);
|
.expect(409);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('alerts', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await esArchiver.load('auditbeat/hosts');
|
||||||
|
await createSignalsIndex(supertest);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await deleteSignalsIndex(supertest);
|
||||||
|
await deleteAllAlerts(supertest);
|
||||||
|
await esArchiver.unload('auditbeat/hosts');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates alert status when the status is updated and syncAlerts=true', async () => {
|
||||||
|
const rule = getRuleForSignalTesting(['auditbeat-*']);
|
||||||
|
|
||||||
|
const { body: postedCase } = await supertest
|
||||||
|
.post(CASES_URL)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send(postCaseReq)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const { id } = await createRule(supertest, rule);
|
||||||
|
await waitForRuleSuccessOrStatus(supertest, id);
|
||||||
|
await waitForSignalsToBePresent(supertest, 1, [id]);
|
||||||
|
const signals = await getSignalsByIds(supertest, [id]);
|
||||||
|
|
||||||
|
const alert = signals.hits.hits[0];
|
||||||
|
expect(alert._source.signal.status).eql('open');
|
||||||
|
|
||||||
|
const { body: caseUpdated } = await supertest
|
||||||
|
.post(`${CASES_URL}/${postedCase.id}/comments`)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send({
|
||||||
|
alertId: alert._id,
|
||||||
|
index: alert._index,
|
||||||
|
type: CommentType.alert,
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
await supertest
|
||||||
|
.patch(CASES_URL)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send({
|
||||||
|
cases: [
|
||||||
|
{
|
||||||
|
id: caseUpdated.id,
|
||||||
|
version: caseUpdated.version,
|
||||||
|
status: 'in-progress',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const { body: updatedAlert } = await supertest
|
||||||
|
.post(DETECTION_ENGINE_QUERY_SIGNALS_URL)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send(getQuerySignalIds([alert._id]))
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(updatedAlert.hits.hits[0]._source.signal.status).eql('in-progress');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does NOT updates alert status when the status is updated and syncAlerts=false', async () => {
|
||||||
|
const rule = getRuleForSignalTesting(['auditbeat-*']);
|
||||||
|
|
||||||
|
const { body: postedCase } = await supertest
|
||||||
|
.post(CASES_URL)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send({ ...postCaseReq, settings: { syncAlerts: false } })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const { id } = await createRule(supertest, rule);
|
||||||
|
await waitForRuleSuccessOrStatus(supertest, id);
|
||||||
|
await waitForSignalsToBePresent(supertest, 1, [id]);
|
||||||
|
const signals = await getSignalsByIds(supertest, [id]);
|
||||||
|
|
||||||
|
const alert = signals.hits.hits[0];
|
||||||
|
expect(alert._source.signal.status).eql('open');
|
||||||
|
|
||||||
|
const { body: caseUpdated } = await supertest
|
||||||
|
.post(`${CASES_URL}/${postedCase.id}/comments`)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send({
|
||||||
|
alertId: alert._id,
|
||||||
|
index: alert._index,
|
||||||
|
type: CommentType.alert,
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
await supertest
|
||||||
|
.patch(CASES_URL)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send({
|
||||||
|
cases: [
|
||||||
|
{
|
||||||
|
id: caseUpdated.id,
|
||||||
|
version: caseUpdated.version,
|
||||||
|
status: 'in-progress',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const { body: updatedAlert } = await supertest
|
||||||
|
.post(DETECTION_ENGINE_QUERY_SIGNALS_URL)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send(getQuerySignalIds([alert._id]))
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(updatedAlert.hits.hits[0]._source.signal.status).eql('open');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it updates alert status when syncAlerts is turned on', async () => {
|
||||||
|
const rule = getRuleForSignalTesting(['auditbeat-*']);
|
||||||
|
|
||||||
|
const { body: postedCase } = await supertest
|
||||||
|
.post(CASES_URL)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send({ ...postCaseReq, settings: { syncAlerts: false } })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const { id } = await createRule(supertest, rule);
|
||||||
|
await waitForRuleSuccessOrStatus(supertest, id);
|
||||||
|
await waitForSignalsToBePresent(supertest, 1, [id]);
|
||||||
|
const signals = await getSignalsByIds(supertest, [id]);
|
||||||
|
|
||||||
|
const alert = signals.hits.hits[0];
|
||||||
|
expect(alert._source.signal.status).eql('open');
|
||||||
|
|
||||||
|
const { body: caseUpdated } = await supertest
|
||||||
|
.post(`${CASES_URL}/${postedCase.id}/comments`)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send({
|
||||||
|
alertId: alert._id,
|
||||||
|
index: alert._index,
|
||||||
|
type: CommentType.alert,
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
// Update the status of the case with sync alerts off
|
||||||
|
const { body: caseStatusUpdated } = await supertest
|
||||||
|
.patch(CASES_URL)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send({
|
||||||
|
cases: [
|
||||||
|
{
|
||||||
|
id: caseUpdated.id,
|
||||||
|
version: caseUpdated.version,
|
||||||
|
status: 'in-progress',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
// Turn sync alerts on
|
||||||
|
await supertest
|
||||||
|
.patch(CASES_URL)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send({
|
||||||
|
cases: [
|
||||||
|
{
|
||||||
|
id: caseStatusUpdated[0].id,
|
||||||
|
version: caseStatusUpdated[0].version,
|
||||||
|
settings: { syncAlerts: true },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const { body: updatedAlert } = await supertest
|
||||||
|
.post(DETECTION_ENGINE_QUERY_SIGNALS_URL)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send(getQuerySignalIds([alert._id]))
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(updatedAlert.hits.hits[0]._source.signal.status).eql('in-progress');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it does NOT updates alert status when syncAlerts is turned off', async () => {
|
||||||
|
const rule = getRuleForSignalTesting(['auditbeat-*']);
|
||||||
|
|
||||||
|
const { body: postedCase } = await supertest
|
||||||
|
.post(CASES_URL)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send(postCaseReq)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const { id } = await createRule(supertest, rule);
|
||||||
|
await waitForRuleSuccessOrStatus(supertest, id);
|
||||||
|
await waitForSignalsToBePresent(supertest, 1, [id]);
|
||||||
|
const signals = await getSignalsByIds(supertest, [id]);
|
||||||
|
|
||||||
|
const alert = signals.hits.hits[0];
|
||||||
|
expect(alert._source.signal.status).eql('open');
|
||||||
|
|
||||||
|
const { body: caseUpdated } = await supertest
|
||||||
|
.post(`${CASES_URL}/${postedCase.id}/comments`)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send({
|
||||||
|
alertId: alert._id,
|
||||||
|
index: alert._index,
|
||||||
|
type: CommentType.alert,
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
// Turn sync alerts off
|
||||||
|
const { body: caseSettingsUpdated } = await supertest
|
||||||
|
.patch(CASES_URL)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send({
|
||||||
|
cases: [
|
||||||
|
{
|
||||||
|
id: caseUpdated.id,
|
||||||
|
version: caseUpdated.version,
|
||||||
|
settings: { syncAlerts: false },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
// Update the status of the case with sync alerts off
|
||||||
|
await supertest
|
||||||
|
.patch(CASES_URL)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send({
|
||||||
|
cases: [
|
||||||
|
{
|
||||||
|
id: caseSettingsUpdated[0].id,
|
||||||
|
version: caseSettingsUpdated[0].version,
|
||||||
|
status: 'in-progress',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const { body: updatedAlert } = await supertest
|
||||||
|
.post(DETECTION_ENGINE_QUERY_SIGNALS_URL)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send(getQuerySignalIds([alert._id]))
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(updatedAlert.hits.hits[0]._source.signal.status).eql('open');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue