[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,
|
||||
caseConfigureSavedObject: mockCaseConfigure,
|
||||
});
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
const res = await caseClient.client.create({ theCase: postCase });
|
||||
|
||||
expect(res).toEqual({
|
||||
|
@ -120,7 +120,7 @@ describe('create', () => {
|
|||
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||
caseSavedObject: mockCases,
|
||||
});
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
const res = await caseClient.client.create({ theCase: postCase });
|
||||
|
||||
expect(res).toEqual({
|
||||
|
@ -165,7 +165,10 @@ describe('create', () => {
|
|||
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||
caseSavedObject: mockCases,
|
||||
});
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient, true);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({
|
||||
savedObjectsClient,
|
||||
badAuth: true,
|
||||
});
|
||||
const res = await caseClient.client.create({ theCase: postCase });
|
||||
|
||||
expect(res).toEqual({
|
||||
|
@ -213,7 +216,7 @@ describe('create', () => {
|
|||
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||
caseSavedObject: mockCases,
|
||||
});
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
caseClient.client
|
||||
// @ts-expect-error
|
||||
.create({ theCase: postCase })
|
||||
|
@ -240,7 +243,7 @@ describe('create', () => {
|
|||
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||
caseSavedObject: mockCases,
|
||||
});
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
caseClient.client
|
||||
// @ts-expect-error
|
||||
.create({ theCase: postCase })
|
||||
|
@ -267,7 +270,7 @@ describe('create', () => {
|
|||
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||
caseSavedObject: mockCases,
|
||||
});
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
caseClient.client
|
||||
// @ts-expect-error
|
||||
.create({ theCase: postCase })
|
||||
|
@ -289,7 +292,7 @@ describe('create', () => {
|
|||
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||
caseSavedObject: mockCases,
|
||||
});
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
caseClient.client
|
||||
// @ts-expect-error
|
||||
.create({ theCase: postCase })
|
||||
|
@ -317,7 +320,7 @@ describe('create', () => {
|
|||
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||
caseSavedObject: mockCases,
|
||||
});
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
caseClient.client
|
||||
// @ts-expect-error
|
||||
.create({ theCase: postCase })
|
||||
|
@ -349,7 +352,7 @@ describe('create', () => {
|
|||
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||
caseSavedObject: mockCases,
|
||||
});
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
caseClient.client.create({ theCase: postCase }).catch((e) => {
|
||||
expect(e).not.toBeNull();
|
||||
expect(e.isBoom).toBe(true);
|
||||
|
@ -375,7 +378,7 @@ describe('create', () => {
|
|||
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||
caseSavedObject: mockCases,
|
||||
});
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
|
||||
caseClient.client.create({ theCase: postCase }).catch((e) => {
|
||||
expect(e).not.toBeNull();
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
createMockSavedObjectsRepository,
|
||||
mockCaseNoConnectorId,
|
||||
mockCases,
|
||||
mockCaseComments,
|
||||
} from '../../routes/api/__fixtures__';
|
||||
import { createCaseClientWithMockSavedObjectsClient } from '../mocks';
|
||||
|
||||
|
@ -37,7 +38,7 @@ describe('update', () => {
|
|||
caseSavedObject: mockCases,
|
||||
});
|
||||
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
const res = await caseClient.client.update({
|
||||
caseClient: caseClient.client,
|
||||
cases: patchCases,
|
||||
|
@ -120,7 +121,7 @@ describe('update', () => {
|
|||
],
|
||||
});
|
||||
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
const res = await caseClient.client.update({
|
||||
caseClient: caseClient.client,
|
||||
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 () => {
|
||||
const patchCases = {
|
||||
cases: [
|
||||
|
@ -171,7 +227,7 @@ describe('update', () => {
|
|||
caseSavedObject: [mockCaseNoConnectorId],
|
||||
});
|
||||
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
const res = await caseClient.client.update({
|
||||
caseClient: caseClient.client,
|
||||
cases: patchCases,
|
||||
|
@ -227,7 +283,7 @@ describe('update', () => {
|
|||
caseSavedObject: mockCases,
|
||||
});
|
||||
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
const res = await caseClient.client.update({
|
||||
caseClient: caseClient.client,
|
||||
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', () => {
|
||||
|
@ -293,7 +547,7 @@ describe('update', () => {
|
|||
caseSavedObject: mockCases,
|
||||
});
|
||||
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
caseClient.client
|
||||
// @ts-expect-error
|
||||
.update({ cases: patchCases })
|
||||
|
@ -324,7 +578,7 @@ describe('update', () => {
|
|||
caseSavedObject: mockCases,
|
||||
});
|
||||
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
caseClient.client
|
||||
// @ts-expect-error
|
||||
.update({ cases: patchCases })
|
||||
|
@ -351,7 +605,7 @@ describe('update', () => {
|
|||
caseSavedObject: mockCases,
|
||||
});
|
||||
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
caseClient.client.update({ caseClient: caseClient.client, cases: patchCases }).catch((e) => {
|
||||
expect(e).not.toBeNull();
|
||||
expect(e.isBoom).toBe(true);
|
||||
|
@ -381,7 +635,7 @@ describe('update', () => {
|
|||
caseSavedObject: mockCases,
|
||||
});
|
||||
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
caseClient.client.update({ caseClient: caseClient.client, cases: patchCases }).catch((e) => {
|
||||
expect(e).not.toBeNull();
|
||||
expect(e.isBoom).toBe(true);
|
||||
|
@ -408,7 +662,7 @@ describe('update', () => {
|
|||
caseSavedObject: mockCases,
|
||||
});
|
||||
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
caseClient.client.update({ caseClient: caseClient.client, cases: patchCases }).catch((e) => {
|
||||
expect(e).not.toBeNull();
|
||||
expect(e.isBoom).toBe(true);
|
||||
|
|
|
@ -110,7 +110,8 @@ export const update = ({
|
|||
};
|
||||
} else if (
|
||||
updateCaseAttributes.status &&
|
||||
updateCaseAttributes.status === CaseStatuses.open
|
||||
(updateCaseAttributes.status === CaseStatuses.open ||
|
||||
updateCaseAttributes.status === CaseStatuses['in-progress'])
|
||||
) {
|
||||
closedInfo = {
|
||||
closed_at: null,
|
||||
|
@ -182,11 +183,14 @@ export const update = ({
|
|||
// The filter guarantees that the comments will be of type alert
|
||||
})) as SavedObjectsFindResponse<{ alertId: string }>;
|
||||
|
||||
caseClient.updateAlertsStatus({
|
||||
ids: caseComments.saved_objects.map(({ attributes: { alertId } }) => alertId),
|
||||
// Either there is a status update or the syncAlerts got turned on.
|
||||
status: theCase.status ?? currentCase?.attributes.status ?? CaseStatuses.open,
|
||||
});
|
||||
const commentIds = caseComments.saved_objects.map(({ attributes: { alertId } }) => alertId);
|
||||
if (commentIds.length > 0) {
|
||||
caseClient.updateAlertsStatus({
|
||||
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
|
||||
|
|
|
@ -29,7 +29,7 @@ describe('addComment', () => {
|
|||
caseCommentSavedObject: mockCaseComments,
|
||||
});
|
||||
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
const res = await caseClient.client.addComment({
|
||||
caseClient: caseClient.client,
|
||||
caseId: 'mock-id-1',
|
||||
|
@ -65,7 +65,7 @@ describe('addComment', () => {
|
|||
caseCommentSavedObject: mockCaseComments,
|
||||
});
|
||||
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
const res = await caseClient.client.addComment({
|
||||
caseClient: caseClient.client,
|
||||
caseId: 'mock-id-1',
|
||||
|
@ -103,7 +103,7 @@ describe('addComment', () => {
|
|||
caseCommentSavedObject: mockCaseComments,
|
||||
});
|
||||
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
const res = await caseClient.client.addComment({
|
||||
caseClient: caseClient.client,
|
||||
caseId: 'mock-id-1',
|
||||
|
@ -127,7 +127,7 @@ describe('addComment', () => {
|
|||
caseCommentSavedObject: mockCaseComments,
|
||||
});
|
||||
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
await caseClient.client.addComment({
|
||||
caseClient: caseClient.client,
|
||||
caseId: 'mock-id-1',
|
||||
|
@ -175,7 +175,10 @@ describe('addComment', () => {
|
|||
caseCommentSavedObject: mockCaseComments,
|
||||
});
|
||||
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient, true);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({
|
||||
savedObjectsClient,
|
||||
badAuth: true,
|
||||
});
|
||||
const res = await caseClient.client.addComment({
|
||||
caseClient: caseClient.client,
|
||||
caseId: 'mock-id-1',
|
||||
|
@ -203,6 +206,66 @@ describe('addComment', () => {
|
|||
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', () => {
|
||||
|
@ -213,7 +276,7 @@ describe('addComment', () => {
|
|||
caseSavedObject: mockCases,
|
||||
caseCommentSavedObject: mockCaseComments,
|
||||
});
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
caseClient.client
|
||||
.addComment({
|
||||
caseId: 'mock-id-1',
|
||||
|
@ -235,7 +298,7 @@ describe('addComment', () => {
|
|||
caseCommentSavedObject: mockCaseComments,
|
||||
});
|
||||
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
const allRequestAttributes = {
|
||||
type: CommentType.user,
|
||||
comment: 'a comment',
|
||||
|
@ -267,7 +330,7 @@ describe('addComment', () => {
|
|||
caseCommentSavedObject: mockCaseComments,
|
||||
});
|
||||
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
|
||||
['alertId', 'index'].forEach((attribute) => {
|
||||
caseClient.client
|
||||
|
@ -296,7 +359,7 @@ describe('addComment', () => {
|
|||
caseCommentSavedObject: mockCaseComments,
|
||||
});
|
||||
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
const allRequestAttributes = {
|
||||
type: CommentType.alert,
|
||||
index: 'test-index',
|
||||
|
@ -329,7 +392,7 @@ describe('addComment', () => {
|
|||
caseCommentSavedObject: mockCaseComments,
|
||||
});
|
||||
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
|
||||
['comment'].forEach((attribute) => {
|
||||
caseClient.client
|
||||
|
@ -358,7 +421,7 @@ describe('addComment', () => {
|
|||
caseSavedObject: mockCases,
|
||||
caseCommentSavedObject: mockCaseComments,
|
||||
});
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
caseClient.client
|
||||
.addComment({
|
||||
caseClient: caseClient.client,
|
||||
|
@ -382,7 +445,7 @@ describe('addComment', () => {
|
|||
caseSavedObject: mockCases,
|
||||
caseCommentSavedObject: mockCaseComments,
|
||||
});
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
caseClient.client
|
||||
.addComment({
|
||||
caseClient: caseClient.client,
|
||||
|
@ -398,5 +461,31 @@ describe('addComment', () => {
|
|||
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({
|
||||
caseMappingsSavedObject: mockCaseMappings,
|
||||
});
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
const res = await caseClient.client.getFields({
|
||||
actionsClient: actionsMock,
|
||||
connectorType: ConnectorTypes.jira,
|
||||
|
@ -43,7 +43,7 @@ describe('get_fields', () => {
|
|||
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||
caseMappingsSavedObject: mockCaseMappings,
|
||||
});
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
await caseClient.client
|
||||
.getFields({
|
||||
actionsClient: { ...actionsMock, execute: jest.fn().mockReturnValue(actionsErrResponse) },
|
||||
|
|
|
@ -27,7 +27,7 @@ describe('get_mappings', () => {
|
|||
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||
caseMappingsSavedObject: mockCaseMappings,
|
||||
});
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
const res = await caseClient.client.getMappings({
|
||||
actionsClient: actionsMock,
|
||||
caseClient: caseClient.client,
|
||||
|
@ -41,7 +41,7 @@ describe('get_mappings', () => {
|
|||
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||
caseMappingsSavedObject: [],
|
||||
});
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient });
|
||||
const res = await caseClient.client.getMappings({
|
||||
actionsClient: actionsMock,
|
||||
caseClient: caseClient.client,
|
||||
|
|
|
@ -4,11 +4,12 @@
|
|||
* 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 { loggingSystemMock, elasticsearchServiceMock } from '../../../../../src/core/server/mocks';
|
||||
import { loggingSystemMock } from '../../../../../src/core/server/mocks';
|
||||
import { actionsClientMock } from '../../../actions/server/mocks';
|
||||
import {
|
||||
AlertService,
|
||||
AlertServiceContract,
|
||||
CaseConfigureService,
|
||||
CaseService,
|
||||
CaseUserActionServiceSetup,
|
||||
|
@ -29,17 +30,24 @@ export const createCaseClientMock = (): CaseClientMock => ({
|
|||
updateAlertsStatus: jest.fn(),
|
||||
});
|
||||
|
||||
export const createCaseClientWithMockSavedObjectsClient = async (
|
||||
savedObjectsClient: any,
|
||||
badAuth: boolean = false
|
||||
): Promise<{
|
||||
export const createCaseClientWithMockSavedObjectsClient = async ({
|
||||
savedObjectsClient,
|
||||
badAuth = false,
|
||||
omitFromContext = [],
|
||||
}: {
|
||||
savedObjectsClient: any;
|
||||
badAuth?: boolean;
|
||||
omitFromContext?: string[];
|
||||
}): Promise<{
|
||||
client: CaseClient;
|
||||
services: { userActionService: jest.Mocked<CaseUserActionServiceSetup> };
|
||||
services: {
|
||||
userActionService: jest.Mocked<CaseUserActionServiceSetup>;
|
||||
alertsService: jest.Mocked<AlertServiceContract>;
|
||||
};
|
||||
}> => {
|
||||
const actionsMock = actionsClientMock.create();
|
||||
actionsMock.getAll.mockImplementation(() => Promise.resolve(getActions()));
|
||||
const log = loggingSystemMock.create().get('case');
|
||||
const esClientMock = elasticsearchServiceMock.createClusterClient();
|
||||
const request = {} as KibanaRequest;
|
||||
|
||||
const caseServicePlugin = new CaseService(log);
|
||||
|
@ -56,10 +64,10 @@ export const createCaseClientWithMockSavedObjectsClient = async (
|
|||
postUserActions: 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: {
|
||||
savedObjects: {
|
||||
client: savedObjectsClient,
|
||||
|
@ -74,7 +82,7 @@ export const createCaseClientWithMockSavedObjectsClient = async (
|
|||
getSignalsIndex: () => '.siem-signals',
|
||||
}),
|
||||
},
|
||||
} as unknown) as RequestHandlerContext;
|
||||
};
|
||||
|
||||
const caseClient = createCaseClient({
|
||||
savedObjectsClient,
|
||||
|
@ -84,10 +92,10 @@ export const createCaseClientWithMockSavedObjectsClient = async (
|
|||
connectorMappingsService,
|
||||
userActionService,
|
||||
alertsService,
|
||||
context,
|
||||
context: (omit(omitFromContext, context) as unknown) as RequestHandlerContext,
|
||||
});
|
||||
return {
|
||||
client: caseClient,
|
||||
services: { userActionService },
|
||||
services: { userActionService, alertsService },
|
||||
};
|
||||
};
|
||||
|
|
|
@ -28,7 +28,7 @@ export const createMockSavedObjectsRepository = ({
|
|||
caseCommentSavedObject?: any[];
|
||||
caseConfigureSavedObject?: any[];
|
||||
caseMappingsSavedObject?: any[];
|
||||
}) => {
|
||||
} = {}) => {
|
||||
const mockSavedObjectsClientContract = ({
|
||||
bulkGet: jest.fn((objects: SavedObjectsBulkGetObject[]) => {
|
||||
return {
|
||||
|
@ -100,9 +100,12 @@ export const createMockSavedObjectsRepository = ({
|
|||
}
|
||||
|
||||
if (
|
||||
findArgs.type === CASE_CONFIGURE_SAVED_OBJECT &&
|
||||
caseConfigureSavedObject[0] &&
|
||||
caseConfigureSavedObject[0].id === 'throw-error-find'
|
||||
(findArgs.type === CASE_CONFIGURE_SAVED_OBJECT &&
|
||||
caseConfigureSavedObject[0] &&
|
||||
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');
|
||||
}
|
||||
|
|
|
@ -348,6 +348,38 @@ export const mockCaseComments: Array<SavedObject<CommentAttributes>> = [
|
|||
updated_at: '2019-11-25T22:32:30.608Z',
|
||||
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>> = [
|
||||
|
|
|
@ -104,7 +104,7 @@ describe('GET case', () => {
|
|||
const response = await routeHandler(theContext, request, kibanaResponseFactory);
|
||||
|
||||
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 () => {
|
||||
|
|
|
@ -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 { mount } from 'enzyme';
|
||||
import { waitFor, act } from '@testing-library/react';
|
||||
import { noop } from 'lodash/fp';
|
||||
|
||||
import { AddComment, AddCommentRefObject } from '.';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router';
|
||||
|
||||
import { CommentRequest, CommentType } from '../../../../../case/common/api';
|
||||
import { useInsertTimeline } from '../use_insert_timeline';
|
||||
import { usePostComment } from '../../containers/use_post_comment';
|
||||
import { AddComment, AddCommentRefObject } from '.';
|
||||
|
||||
jest.mock('../../containers/use_post_comment');
|
||||
jest.mock('../use_insert_timeline');
|
||||
|
@ -34,7 +35,7 @@ const addCommentProps = {
|
|||
showLoading: false,
|
||||
};
|
||||
|
||||
const defaultPostCommment = {
|
||||
const defaultPostComment = {
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
postComment,
|
||||
|
@ -48,7 +49,7 @@ const sampleData: CommentRequest = {
|
|||
describe('AddComment ', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
usePostCommentMock.mockImplementation(() => defaultPostCommment);
|
||||
usePostCommentMock.mockImplementation(() => defaultPostComment);
|
||||
jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation);
|
||||
});
|
||||
|
||||
|
@ -83,7 +84,7 @@ describe('AddComment ', () => {
|
|||
});
|
||||
|
||||
it('should render spinner and disable submit when loading', () => {
|
||||
usePostCommentMock.mockImplementation(() => ({ ...defaultPostCommment, isLoading: true }));
|
||||
usePostCommentMock.mockImplementation(() => ({ ...defaultPostComment, isLoading: true }));
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<Router history={mockHistory}>
|
||||
|
@ -99,7 +100,7 @@ describe('AddComment ', () => {
|
|||
});
|
||||
|
||||
it('should disable submit button when disabled prop passed', () => {
|
||||
usePostCommentMock.mockImplementation(() => ({ ...defaultPostCommment, isLoading: true }));
|
||||
usePostCommentMock.mockImplementation(() => ({ ...defaultPostComment, isLoading: true }));
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<Router history={mockHistory}>
|
||||
|
@ -141,8 +142,9 @@ describe('AddComment ', () => {
|
|||
});
|
||||
|
||||
it('it should insert a timeline', async () => {
|
||||
let attachTimeline = noop;
|
||||
useInsertTimelineMock.mockImplementation((comment, onTimelineAttached) => {
|
||||
onTimelineAttached(`[title](url)`);
|
||||
attachTimeline = onTimelineAttached;
|
||||
});
|
||||
|
||||
const wrapper = mount(
|
||||
|
@ -153,6 +155,10 @@ describe('AddComment ', () => {
|
|||
</TestProviders>
|
||||
);
|
||||
|
||||
act(() => {
|
||||
attachTimeline('[title](url)');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
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 { waitFor } from '@testing-library/react';
|
||||
import '../../../common/mock/match_media';
|
||||
import { AllCases } from '.';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
import { useGetCasesMockState } from '../../containers/mock';
|
||||
import { casesStatus, useGetCasesMockState } from '../../containers/mock';
|
||||
import * as i18n from './translations';
|
||||
|
||||
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 { useUpdateCases } from '../../containers/use_bulk_update_case';
|
||||
import { getCasesColumns } from './columns';
|
||||
import { AllCases } from '.';
|
||||
|
||||
jest.mock('../../containers/use_bulk_update_case');
|
||||
jest.mock('../../containers/use_delete_cases');
|
||||
|
@ -61,6 +61,7 @@ describe('AllCases', () => {
|
|||
setQueryParams,
|
||||
setSelectedCases,
|
||||
};
|
||||
|
||||
const defaultDeleteCases = {
|
||||
dispatchResetIsDeleted,
|
||||
handleOnDeleteConfirm,
|
||||
|
@ -69,13 +70,14 @@ describe('AllCases', () => {
|
|||
isDisplayConfirmDeleteModal: false,
|
||||
isLoading: false,
|
||||
};
|
||||
|
||||
const defaultCasesStatus = {
|
||||
countClosedCases: 0,
|
||||
countOpenCases: 5,
|
||||
...casesStatus,
|
||||
fetchCasesStatus,
|
||||
isError: false,
|
||||
isLoading: true,
|
||||
isLoading: false,
|
||||
};
|
||||
|
||||
const defaultUpdateCases = {
|
||||
isUpdated: false,
|
||||
isLoading: false,
|
||||
|
@ -103,6 +105,7 @@ describe('AllCases', () => {
|
|||
<AllCases userCanCrud={true} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(wrapper.find(`a[data-test-subj="case-details-link"]`).first().prop('href')).toEqual(
|
||||
`/${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 () => {
|
||||
useGetCasesMock.mockReturnValue({
|
||||
...defaultGetCases,
|
||||
|
@ -199,6 +259,7 @@ describe('AllCases', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('closes case when row action icon clicked', async () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
|
@ -217,6 +278,7 @@ describe('AllCases', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('opens case when row action icon clicked', async () => {
|
||||
useGetCasesMock.mockReturnValue({
|
||||
...defaultGetCases,
|
||||
|
@ -240,6 +302,7 @@ describe('AllCases', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Bulk delete', async () => {
|
||||
useGetCasesMock.mockReturnValue({
|
||||
...defaultGetCases,
|
||||
|
@ -277,6 +340,7 @@ describe('AllCases', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('Bulk close status update', async () => {
|
||||
useGetCasesMock.mockReturnValue({
|
||||
...defaultGetCases,
|
||||
|
@ -294,6 +358,7 @@ describe('AllCases', () => {
|
|||
expect(updateBulkStatus).toBeCalledWith(useGetCasesMockState.data.cases, CaseStatuses.closed);
|
||||
});
|
||||
});
|
||||
|
||||
it('Bulk open status update', async () => {
|
||||
useGetCasesMock.mockReturnValue({
|
||||
...defaultGetCases,
|
||||
|
@ -315,6 +380,7 @@ describe('AllCases', () => {
|
|||
expect(updateBulkStatus).toBeCalledWith(useGetCasesMockState.data.cases, CaseStatuses.open);
|
||||
});
|
||||
});
|
||||
|
||||
it('isDeleted is true, refetch', async () => {
|
||||
useDeleteCasesMock.mockReturnValue({
|
||||
...defaultDeleteCases,
|
||||
|
@ -492,4 +558,73 @@ describe('AllCases', () => {
|
|||
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 { TestProviders } from '../../../common/mock';
|
||||
import { basicCase, basicPush } from '../../containers/mock';
|
||||
import { CaseViewActions } from './actions';
|
||||
import * as i18n from './translations';
|
||||
import { Actions } from './actions';
|
||||
import * as i18n from '../case_view/translations';
|
||||
jest.mock('../../containers/use_delete_cases');
|
||||
const useDeleteCasesMock = useDeleteCases as jest.Mock;
|
||||
|
||||
|
@ -39,14 +39,16 @@ describe('CaseView actions', () => {
|
|||
isDeleted: false,
|
||||
isDisplayConfirmDeleteModal: false,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
useDeleteCasesMock.mockImplementation(() => defaultDeleteState);
|
||||
});
|
||||
|
||||
it('clicking trash toggles modal', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<CaseViewActions caseData={basicCase} currentExternalIncident={null} />
|
||||
<Actions caseData={basicCase} currentExternalIncident={null} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
|
@ -56,6 +58,7 @@ describe('CaseView actions', () => {
|
|||
wrapper.find('button[data-test-subj="property-actions-trash"]').simulate('click');
|
||||
expect(handleToggleModal).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('toggle delete modal and confirm', () => {
|
||||
useDeleteCasesMock.mockImplementation(() => ({
|
||||
...defaultDeleteState,
|
||||
|
@ -63,7 +66,7 @@ describe('CaseView actions', () => {
|
|||
}));
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<CaseViewActions caseData={basicCase} currentExternalIncident={null} />
|
||||
<Actions caseData={basicCase} currentExternalIncident={null} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
|
@ -73,10 +76,11 @@ describe('CaseView actions', () => {
|
|||
{ id: basicCase.id, title: basicCase.title },
|
||||
]);
|
||||
});
|
||||
|
||||
it('displays active incident link', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<CaseViewActions
|
||||
<Actions
|
||||
caseData={basicCase}
|
||||
currentExternalIncident={{
|
||||
...basicPush,
|
|
@ -7,7 +7,7 @@
|
|||
import { isEmpty } from 'lodash/fp';
|
||||
import React, { useMemo } from 'react';
|
||||
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 { ConfirmDeleteCaseModal } from '../confirm_delete_case';
|
||||
import { PropertyActions } from '../property_actions';
|
||||
|
@ -20,7 +20,7 @@ interface CaseViewActions {
|
|||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const CaseViewActionsComponent: React.FC<CaseViewActions> = ({
|
||||
const ActionsComponent: React.FC<CaseViewActions> = ({
|
||||
caseData,
|
||||
currentExternalIncident,
|
||||
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 * as i18n from '../case_view/translations';
|
||||
import { FormattedRelativePreferenceDate } from '../../../common/components/formatted_date';
|
||||
import { CaseViewActions } from '../case_view/actions';
|
||||
import { Actions } from './actions';
|
||||
import { Case } from '../../containers/types';
|
||||
import { CaseService } from '../../containers/use_get_case_user_actions';
|
||||
import { StatusContextMenu } from './status_context_menu';
|
||||
|
@ -124,8 +124,8 @@ const CaseActionBarComponent: React.FC<CaseActionBarProps> = ({
|
|||
{i18n.CASE_REFRESH}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<CaseViewActions
|
||||
<EuiFlexItem grow={false} data-test-subj="case-view-actions">
|
||||
<Actions
|
||||
caseData={caseData}
|
||||
currentExternalIncident={currentExternalIncident}
|
||||
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}
|
||||
onChange={onChange}
|
||||
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 { Router, routeData, mockHistory, mockLocation } from '../__mock__/router';
|
||||
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 { useUpdateCase } from '../../containers/use_update_case';
|
||||
import { useGetCase } from '../../containers/use_get_case';
|
||||
|
@ -51,6 +57,7 @@ export const caseProps: CaseProps = {
|
|||
userCanCrud: true,
|
||||
caseData: {
|
||||
...basicCase,
|
||||
comments: [...basicCase.comments, alertComment],
|
||||
connector: {
|
||||
id: 'resilient-2',
|
||||
name: 'Resilient',
|
||||
|
@ -67,6 +74,33 @@ export const caseClosedProps: CaseProps = {
|
|||
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 ', () => {
|
||||
const updateCaseProperty = jest.fn();
|
||||
const fetchCaseUserActions = jest.fn();
|
||||
|
@ -91,7 +125,7 @@ describe('CaseView ', () => {
|
|||
};
|
||||
|
||||
const defaultUseGetCaseUserActions = {
|
||||
caseUserActions,
|
||||
caseUserActions: [...caseUserActions, getAlertUserAction()],
|
||||
caseServices: {},
|
||||
fetchCaseUserActions,
|
||||
firstIndexPushToService: -1,
|
||||
|
@ -103,6 +137,7 @@ describe('CaseView ', () => {
|
|||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.resetAllMocks();
|
||||
useUpdateCaseMock.mockImplementation(() => defaultUpdateCaseState);
|
||||
|
||||
|
@ -111,8 +146,8 @@ describe('CaseView ', () => {
|
|||
usePostPushToServiceMock.mockImplementation(() => ({ isLoading: false, postPushToService }));
|
||||
useConnectorsMock.mockImplementation(() => ({ connectors: connectorsMock, isLoading: false }));
|
||||
useQueryAlertsMock.mockImplementation(() => ({
|
||||
isLoading: false,
|
||||
alerts: { hits: { hists: [] } },
|
||||
loading: false,
|
||||
data: { hits: { hits: alertsHit } },
|
||||
}));
|
||||
});
|
||||
|
||||
|
@ -124,6 +159,7 @@ describe('CaseView ', () => {
|
|||
</Router>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(wrapper.find(`[data-test-subj="case-view-title"]`).first().prop('title')).toEqual(
|
||||
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(
|
||||
<TestProviders>
|
||||
<Router history={mockHistory}>
|
||||
|
@ -204,7 +240,11 @@ describe('CaseView ', () => {
|
|||
.find('button[data-test-subj="case-view-status-dropdown-closed"]')
|
||||
.first()
|
||||
.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}
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={6}>
|
||||
{initLoadingData && <EuiLoadingContent lines={8} />}
|
||||
{initLoadingData && (
|
||||
<EuiLoadingContent lines={8} data-test-subj="case-view-loading-content" />
|
||||
)}
|
||||
{!initLoadingData && (
|
||||
<>
|
||||
<UserActionTree
|
||||
|
|
|
@ -15,6 +15,7 @@ import { Connector } from './connector';
|
|||
import { useConnectors } from '../../containers/configure/use_connectors';
|
||||
import { useGetIncidentTypes } from '../settings/resilient/use_get_incident_types';
|
||||
import { useGetSeverity } from '../settings/resilient/use_get_severity';
|
||||
import { schema, FormProps } from './schema';
|
||||
|
||||
jest.mock('../../../common/lib/kibana', () => {
|
||||
return {
|
||||
|
@ -70,8 +71,12 @@ describe('Connector', () => {
|
|||
let globalForm: FormHook;
|
||||
|
||||
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 },
|
||||
schema: {
|
||||
connectorId: schema.connectorId,
|
||||
fields: schema.fields,
|
||||
},
|
||||
});
|
||||
|
||||
globalForm = form;
|
||||
|
@ -96,7 +101,14 @@ describe('Connector', () => {
|
|||
expect(wrapper.find(`[data-test-subj="caseConnectors"]`).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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,13 +10,17 @@ import { act } from '@testing-library/react';
|
|||
|
||||
import { useForm, Form, FormHook } from '../../../shared_imports';
|
||||
import { Description } from './description';
|
||||
import { schema, FormProps } from './schema';
|
||||
|
||||
describe('Description', () => {
|
||||
let globalForm: FormHook;
|
||||
|
||||
const MockHookWrapperComponent: React.FC = ({ children }) => {
|
||||
const { form } = useForm<{ description: string }>({
|
||||
const { form } = useForm<FormProps>({
|
||||
defaultValue: { description: 'My description' },
|
||||
schema: {
|
||||
description: schema.description,
|
||||
},
|
||||
});
|
||||
|
||||
globalForm = form;
|
||||
|
@ -41,7 +45,7 @@ describe('Description', () => {
|
|||
it('it changes the description', async () => {
|
||||
const wrapper = mount(
|
||||
<MockHookWrapperComponent>
|
||||
<Description isLoading={true} />
|
||||
<Description isLoading={false} />
|
||||
</MockHookWrapperComponent>
|
||||
);
|
||||
|
||||
|
|
|
@ -6,8 +6,9 @@
|
|||
|
||||
import React from 'react';
|
||||
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 { useConnectors } from '../../containers/configure/use_connectors';
|
||||
import { connectorsMock } from '../../containers/mock';
|
||||
|
@ -29,6 +30,7 @@ const initialCaseValue: FormProps = {
|
|||
};
|
||||
|
||||
describe('CreateCaseForm', () => {
|
||||
let globalForm: FormHook;
|
||||
const MockHookWrapperComponent: React.FC = ({ children }) => {
|
||||
const { form } = useForm<FormProps>({
|
||||
defaultValue: initialCaseValue,
|
||||
|
@ -36,6 +38,8 @@ describe('CreateCaseForm', () => {
|
|||
schema,
|
||||
});
|
||||
|
||||
globalForm = 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();
|
||||
});
|
||||
|
||||
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 { mount, ReactWrapper } from 'enzyme';
|
||||
import { act, waitFor } from '@testing-library/react';
|
||||
import { noop } from 'lodash/fp';
|
||||
import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
|
||||
|
||||
import { CasePostRequest } 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 { ConnectorTypes } from '../../../../../case/common/api/connectors';
|
||||
import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router';
|
||||
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 { useInsertTimeline } from '../use_insert_timeline';
|
||||
import {
|
||||
sampleConnectorData,
|
||||
sampleData,
|
||||
sampleTags,
|
||||
useGetIncidentTypesResponse,
|
||||
useGetSeverityResponse,
|
||||
useGetIssueTypesResponse,
|
||||
useGetFieldsByIssueTypeResponse,
|
||||
} from './mock';
|
||||
import { Create } from '.';
|
||||
|
||||
jest.mock('../../containers/use_post_case');
|
||||
jest.mock('../../containers/api');
|
||||
jest.mock('../../containers/use_get_tags');
|
||||
jest.mock('../../containers/configure/use_connectors');
|
||||
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_single_issue');
|
||||
jest.mock('../settings/jira/use_get_issues');
|
||||
jest.mock('../use_insert_timeline');
|
||||
|
||||
const useConnectorsMock = useConnectors 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 useGetSeverityMock = useGetSeverity as jest.Mock;
|
||||
const useGetIssueTypesMock = useGetIssueTypes 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 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,
|
||||
},
|
||||
};
|
||||
const fillForm = (wrapper: ReactWrapper) => {
|
||||
wrapper
|
||||
.find(`[data-test-subj="caseTitle"] input`)
|
||||
.first()
|
||||
.simulate('change', { target: { value: sampleData.title } });
|
||||
|
||||
const defaultPostCase = {
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
caseData: null,
|
||||
postCase,
|
||||
};
|
||||
wrapper
|
||||
.find(`[data-test-subj="caseDescription"] textarea`)
|
||||
.first()
|
||||
.simulate('change', { target: { value: sampleData.description } });
|
||||
|
||||
const sampleConnectorData = { loading: false, connectors: [] };
|
||||
|
||||
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(() => {
|
||||
act(() => {
|
||||
((wrapper.find(EuiComboBox).props() as unknown) as {
|
||||
onChange: (a: EuiComboBoxOptionOption[]) => void;
|
||||
}).onChange(sampleTags.map((tag) => ({ label: tag })));
|
||||
|
@ -161,381 +73,83 @@ const fillForm = async (wrapper: ReactWrapper) => {
|
|||
};
|
||||
|
||||
describe('Create case', () => {
|
||||
const fetchTags = jest.fn();
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
usePostCaseMock.mockImplementation(() => defaultPostCase);
|
||||
jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation);
|
||||
useConnectorsMock.mockReturnValue(sampleConnectorData);
|
||||
useCaseConfigureMock.mockImplementation(() => useCaseConfigureResponse);
|
||||
useGetIncidentTypesMock.mockReturnValue(useGetIncidentTypesResponse);
|
||||
useGetSeverityMock.mockReturnValue(useGetSeverityResponse);
|
||||
useGetIssueTypesMock.mockReturnValue(useGetIssueTypesResponse);
|
||||
useGetFieldsByIssueTypeMock.mockReturnValue(useGetFieldsByIssueTypeResponse);
|
||||
|
||||
jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation);
|
||||
(useGetTags as jest.Mock).mockImplementation(() => ({
|
||||
useGetTagsMock.mockImplementation(() => ({
|
||||
tags: sampleTags,
|
||||
fetchTags,
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Step 1 - Case Fields', () => {
|
||||
it('it renders', async () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<Router history={mockHistory}>
|
||||
<Create />
|
||||
</Router>
|
||||
</TestProviders>
|
||||
);
|
||||
it('it renders', async () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<Router history={mockHistory}>
|
||||
<Create />
|
||||
</Router>
|
||||
</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="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));
|
||||
});
|
||||
expect(wrapper.find(`[data-test-subj="create-case-submit"]`).exists()).toBeTruthy();
|
||||
expect(wrapper.find(`[data-test-subj="create-case-cancel"]`).exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('Step 2 - Connector Fields', () => {
|
||||
it(`it should submit a Jira connector`, async () => {
|
||||
useConnectorsMock.mockReturnValue({
|
||||
...sampleConnectorData,
|
||||
connectors: connectorsMock,
|
||||
});
|
||||
it('should redirect to all cases on cancel click', async () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<Router history={mockHistory}>
|
||||
<Create />
|
||||
</Router>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
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('/'));
|
||||
});
|
||||
|
||||
await fillForm(wrapper);
|
||||
await waitFor(() => {
|
||||
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');
|
||||
wrapper.update();
|
||||
});
|
||||
it('should redirect to new case when posting the case', async () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<Router history={mockHistory}>
|
||||
<Create />
|
||||
</Router>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
wrapper.update();
|
||||
expect(wrapper.find(`[data-test-subj="connector-settings-jira"]`).exists()).toBeTruthy();
|
||||
});
|
||||
fillForm(wrapper);
|
||||
wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click');
|
||||
|
||||
act(() => {
|
||||
wrapper
|
||||
.find('select[data-test-subj="issueTypeSelect"]')
|
||||
.first()
|
||||
.simulate('change', {
|
||||
target: { value: '10007' },
|
||||
});
|
||||
});
|
||||
await waitFor(() => expect(mockHistory.push).toHaveBeenNthCalledWith(1, '/basic-case-id'));
|
||||
});
|
||||
|
||||
act(() => {
|
||||
wrapper
|
||||
.find('select[data-test-subj="prioritySelect"]')
|
||||
.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: 'jira-1',
|
||||
name: 'Jira',
|
||||
type: '.jira',
|
||||
fields: { issueType: '10007', parent: null, priority: '2' },
|
||||
},
|
||||
})
|
||||
);
|
||||
it('it should insert a timeline', async () => {
|
||||
let attachTimeline = noop;
|
||||
useInsertTimelineMock.mockImplementation((value, onTimelineAttached) => {
|
||||
attachTimeline = onTimelineAttached;
|
||||
});
|
||||
|
||||
it(`it should submit a resilient connector`, async () => {
|
||||
useConnectorsMock.mockReturnValue({
|
||||
...sampleConnectorData,
|
||||
connectors: connectorsMock,
|
||||
});
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<Router history={mockHistory}>
|
||||
<Create />
|
||||
</Router>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<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' },
|
||||
},
|
||||
})
|
||||
);
|
||||
act(() => {
|
||||
attachTimeline('[title](url)');
|
||||
});
|
||||
|
||||
it(`it should submit a servicenow connector`, async () => {
|
||||
useConnectorsMock.mockReturnValue({
|
||||
...sampleConnectorData,
|
||||
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' },
|
||||
},
|
||||
})
|
||||
await waitFor(() => {
|
||||
expect(wrapper.find(`[data-test-subj="caseDescription"] textarea`).text()).toBe(
|
||||
'[title](url)'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 { SubmitCaseButton } from './submit_button';
|
||||
import { schema, FormProps } from './schema';
|
||||
|
||||
describe('SubmitCaseButton', () => {
|
||||
const onSubmit = jest.fn();
|
||||
|
||||
const MockHookWrapperComponent: React.FC = ({ children }) => {
|
||||
const { form } = useForm<{ title: string }>({
|
||||
const { form } = useForm<FormProps>({
|
||||
defaultValue: { title: 'My title' },
|
||||
schema: {
|
||||
title: schema.title,
|
||||
},
|
||||
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 { 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 { Tags } from './tags';
|
||||
import { schema, FormProps } from './schema';
|
||||
|
||||
jest.mock('../../containers/use_get_tags');
|
||||
const useGetTagsMock = useGetTags as jest.Mock;
|
||||
|
@ -20,10 +21,10 @@ describe('Tags', () => {
|
|||
let globalForm: FormHook;
|
||||
|
||||
const MockHookWrapperComponent: React.FC = ({ children }) => {
|
||||
const { form } = useForm<{ tags: string[] }>({
|
||||
const { form } = useForm<FormProps>({
|
||||
defaultValue: { tags: [] },
|
||||
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 { Title } from './title';
|
||||
import { schema, FormProps } from './schema';
|
||||
|
||||
describe('Title', () => {
|
||||
let globalForm: FormHook;
|
||||
|
||||
const MockHookWrapperComponent: React.FC = ({ children }) => {
|
||||
const { form } = useForm<{ title: string }>({
|
||||
const { form } = useForm<FormProps>({
|
||||
defaultValue: { title: 'My title' },
|
||||
schema: {
|
||||
title: schema.title,
|
||||
},
|
||||
});
|
||||
|
||||
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 (
|
||||
<EuiButton
|
||||
data-test-subj={'case-view-status-action-button'}
|
||||
data-test-subj="case-view-status-action-button"
|
||||
iconType={statuses[caseStatuses[nextStatusIndex]].button.icon}
|
||||
isDisabled={disabled}
|
||||
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,
|
||||
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 (
|
||||
<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 { CreateCaseModal } from './create_case_modal';
|
||||
|
||||
interface Props {
|
||||
export interface UseCreateCaseModalProps {
|
||||
onCaseCreated: (theCase: Case) => void;
|
||||
}
|
||||
export interface UseAllCasesModalReturnedValues {
|
||||
export interface UseCreateCaseModalReturnedValues {
|
||||
modal: JSX.Element;
|
||||
isModalOpen: boolean;
|
||||
closeModal: () => void;
|
||||
openModal: () => void;
|
||||
}
|
||||
|
||||
export const useCreateCaseModal = ({ onCaseCreated }: Props) => {
|
||||
export const useCreateCaseModal = ({ onCaseCreated }: UseCreateCaseModalProps) => {
|
||||
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
|
||||
const closeModal = useCallback(() => setIsModalOpen(false), []);
|
||||
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 { setInsertTimeline } from '../../../timelines/store/timeline/actions';
|
||||
|
||||
interface UseInsertTimelineReturn {
|
||||
export interface UseInsertTimelineReturn {
|
||||
handleOnTimelineChange: (title: string, id: string | null, graphEventId?: string) => void;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import { CaseStatuses } from '../../../../../case/common/api';
|
||||
import { basicPush, getUserAction } from '../../containers/mock';
|
||||
import { getLabelTitle, getPushedServiceLabelTitle, getConnectorLabelTitle } from './helpers';
|
||||
import { mount } from 'enzyme';
|
||||
import { connectorsMock } from '../../containers/configure/mock';
|
||||
import * as i18n from './translations';
|
||||
|
||||
|
@ -56,24 +56,52 @@ describe('User action tree helpers', () => {
|
|||
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 result: string | JSX.Element = getLabelTitle({
|
||||
action,
|
||||
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 result: string | JSX.Element = getLabelTitle({
|
||||
action,
|
||||
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', () => {
|
||||
|
|
|
@ -31,9 +31,13 @@ interface LabelTitle {
|
|||
field: string;
|
||||
}
|
||||
|
||||
const getStatusTitle = (status: CaseStatuses) => {
|
||||
const getStatusTitle = (id: string, status: CaseStatuses) => {
|
||||
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}>
|
||||
<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) => {
|
||||
if (field === 'tags') {
|
||||
return getTagsLabelTitle(action);
|
||||
|
@ -52,12 +59,12 @@ export const getLabelTitle = ({ action, field }: LabelTitle) => {
|
|||
} else if (field === 'description' && action.action === 'update') {
|
||||
return `${i18n.EDITED_FIELD} ${i18n.DESCRIPTION.toLowerCase()}`;
|
||||
} else if (field === 'status' && action.action === 'update') {
|
||||
if (!Object.prototype.hasOwnProperty.call(statuses, action.newValue ?? '')) {
|
||||
return '';
|
||||
const status = action.newValue ?? '';
|
||||
if (isStatusValid(status)) {
|
||||
return getStatusTitle(action.actionId, status);
|
||||
}
|
||||
|
||||
// The above check ensures that the newValue is of type CaseStatuses.
|
||||
return getStatusTitle(action.newValue as CaseStatuses);
|
||||
return '';
|
||||
} else if (field === 'comment' && action.action === 'update') {
|
||||
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 ? (
|
||||
<>
|
||||
{`${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}</>
|
||||
|
|
|
@ -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',
|
||||
};
|
||||
|
||||
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 = {
|
||||
closedAt: null,
|
||||
closedBy: null,
|
||||
|
@ -311,6 +325,15 @@ export const getUserAction = (af: UserActionField, a: UserAction) => ({
|
|||
: 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[] = [
|
||||
getUserAction(['description'], 'create'),
|
||||
getUserAction(['comment'], 'create'),
|
||||
|
|
|
@ -10,7 +10,7 @@ export { getDetectionEngineUrl } from '../redirect_to_detection_engine';
|
|||
export { getAppOverviewUrl } from '../redirect_to_overview';
|
||||
export { getHostDetailsUrl, getHostsUrl } from '../redirect_to_hosts';
|
||||
export { getNetworkUrl, getNetworkDetailsUrl } from '../redirect_to_network';
|
||||
export { getTimelinesUrl, getTimelineTabsUrl } from '../redirect_to_timelines';
|
||||
export { getTimelinesUrl, getTimelineTabsUrl, getTimelineUrl } from '../redirect_to_timelines';
|
||||
export {
|
||||
getCaseDetailsUrl,
|
||||
getCaseUrl,
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
/* eslint-disable react/display-name */
|
||||
import { mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
|
@ -11,11 +13,19 @@ import { DEFAULT_ACTIONS_COLUMN_WIDTH } from '../constants';
|
|||
import * as i18n from '../translations';
|
||||
|
||||
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';
|
||||
|
||||
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', () => {
|
||||
(useShallowEqualSelector as jest.Mock).mockReturnValue(TimelineType.default);
|
||||
|
||||
|
@ -49,7 +59,7 @@ describe('EventColumnView', () => {
|
|||
showCheckboxes: false,
|
||||
showNotes: false,
|
||||
tabType: TimelineTabs.query,
|
||||
timelineId: 'timeline-test',
|
||||
timelineId: TimelineId.active,
|
||||
toggleShowNotes: jest.fn(),
|
||||
updateNote: jest.fn(),
|
||||
isEventPinned: false,
|
||||
|
@ -107,4 +117,39 @@ describe('EventColumnView', () => {
|
|||
|
||||
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 { 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 {
|
||||
defaultUser,
|
||||
|
@ -17,10 +18,22 @@ import {
|
|||
postCommentAlertReq,
|
||||
} from '../../../../common/lib/mock';
|
||||
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
|
||||
export default ({ getService }: FtrProviderContext): void => {
|
||||
const supertest = getService('supertest');
|
||||
const esArchiver = getService('esArchiver');
|
||||
const es = getService('es');
|
||||
|
||||
describe('post_comment', () => {
|
||||
|
@ -166,5 +179,146 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
})
|
||||
.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 { 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 {
|
||||
defaultUser,
|
||||
postCaseReq,
|
||||
|
@ -15,10 +17,22 @@ import {
|
|||
removeServerGeneratedPropertiesFromCase,
|
||||
} from '../../../common/lib/mock';
|
||||
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
|
||||
export default ({ getService }: FtrProviderContext): void => {
|
||||
const supertest = getService('supertest');
|
||||
const esArchiver = getService('esArchiver');
|
||||
const es = getService('es');
|
||||
|
||||
describe('patch_cases', () => {
|
||||
|
@ -248,5 +262,250 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
})
|
||||
.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