[Cases] Adding feature flag for sub cases (#95122)

* Adding feature flag for sub cases

* Disabling case as a connector in security solution

* Fix connector test

* Switching feature flag to global variable

* Removing this.config use

* Fixing circular import and renaming flag
This commit is contained in:
Jonathan Buttner 2021-03-25 13:36:25 -04:00 committed by GitHub
parent b9ef084130
commit 07f32d03b3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 1271 additions and 901 deletions

View file

@ -5,8 +5,6 @@
* 2.0.
*/
import { DEFAULT_MAX_SIGNALS } from '../../security_solution/common/constants';
export const APP_ID = 'cases';
/**
@ -53,5 +51,12 @@ export const SUPPORTED_CONNECTORS = [
* Alerts
*/
// this value is from x-pack/plugins/security_solution/common/constants.ts
const DEFAULT_MAX_SIGNALS = 100;
export const MAX_ALERTS_PER_SUB_CASE = 5000;
export const MAX_GENERATED_ALERTS_PER_SUB_CASE = MAX_ALERTS_PER_SUB_CASE / DEFAULT_MAX_SIGNALS;
/**
* This flag governs enabling the case as a connector feature. It is disabled by default as the feature is not complete.
*/
export const ENABLE_CASE_CONNECTOR = false;

View file

@ -35,6 +35,7 @@ import {
CaseUserActionServiceSetup,
} from '../../services';
import { createCaseError } from '../../common/error';
import { ENABLE_CASE_CONNECTOR } from '../../../common/constants';
interface CreateCaseArgs {
caseConfigureService: CaseConfigureServiceSetup;
@ -60,9 +61,19 @@ export const create = async ({
}: CreateCaseArgs): Promise<CaseResponse> => {
// default to an individual case if the type is not defined.
const { type = CaseType.individual, ...nonTypeCaseFields } = theCase;
if (!ENABLE_CASE_CONNECTOR && type === CaseType.collection) {
throw Boom.badRequest(
'Case type cannot be collection when the case connector feature is disabled'
);
}
const query = pipe(
// decode with the defaulted type field
excess(CasesClientPostRequestRt).decode({ type, ...nonTypeCaseFields }),
excess(CasesClientPostRequestRt).decode({
type,
...nonTypeCaseFields,
}),
fold(throwErrors(Boom.badRequest), identity)
);

View file

@ -5,12 +5,13 @@
* 2.0.
*/
import { SavedObjectsClientContract, Logger } from 'kibana/server';
import { SavedObjectsClientContract, Logger, SavedObject } from 'kibana/server';
import { flattenCaseSavedObject } from '../../routes/api/utils';
import { CaseResponseRt, CaseResponse } from '../../../common/api';
import { CaseResponseRt, CaseResponse, ESCaseAttributes } from '../../../common/api';
import { CaseServiceSetup } from '../../services';
import { countAlertsForID } from '../../common';
import { createCaseError } from '../../common/error';
import { ENABLE_CASE_CONNECTOR } from '../../../common/constants';
interface GetParams {
savedObjectsClient: SavedObjectsClientContract;
@ -33,15 +34,26 @@ export const get = async ({
includeSubCaseComments = false,
}: GetParams): Promise<CaseResponse> => {
try {
const [theCase, subCasesForCaseId] = await Promise.all([
caseService.getCase({
let theCase: SavedObject<ESCaseAttributes>;
let subCaseIds: string[] = [];
if (ENABLE_CASE_CONNECTOR) {
const [caseInfo, subCasesForCaseId] = await Promise.all([
caseService.getCase({
client: savedObjectsClient,
id,
}),
caseService.findSubCasesByCaseId({ client: savedObjectsClient, ids: [id] }),
]);
theCase = caseInfo;
subCaseIds = subCasesForCaseId.saved_objects.map((so) => so.id);
} else {
theCase = await caseService.getCase({
client: savedObjectsClient,
id,
}),
caseService.findSubCasesByCaseId({ client: savedObjectsClient, ids: [id] }),
]);
const subCaseIds = subCasesForCaseId.saved_objects.map((so) => so.id);
});
}
if (!includeComments) {
return CaseResponseRt.encode(
@ -58,7 +70,7 @@ export const get = async ({
sortField: 'created_at',
sortOrder: 'asc',
},
includeSubCaseComments,
includeSubCaseComments: ENABLE_CASE_CONNECTOR && includeSubCaseComments,
});
return CaseResponseRt.encode(

View file

@ -40,6 +40,7 @@ import {
} from '../../services';
import { CasesClientHandler } from '../client';
import { createCaseError } from '../../common/error';
import { ENABLE_CASE_CONNECTOR } from '../../../common/constants';
/**
* Returns true if the case should be closed based on the configuration settings and whether the case
@ -92,7 +93,11 @@ export const push = async ({
try {
[theCase, connector, userActions] = await Promise.all([
casesClient.get({ id: caseId, includeComments: true, includeSubCaseComments: true }),
casesClient.get({
id: caseId,
includeComments: true,
includeSubCaseComments: ENABLE_CASE_CONNECTOR,
}),
actionsClient.get({ id: connectorId }),
casesClient.getUserActions({ caseId }),
]);
@ -183,7 +188,7 @@ export const push = async ({
page: 1,
perPage: theCase?.totalComment ?? 0,
},
includeSubCaseComments: true,
includeSubCaseComments: ENABLE_CASE_CONNECTOR,
}),
]);
} catch (e) {

View file

@ -55,6 +55,7 @@ import { CasesClientHandler } from '..';
import { createAlertUpdateRequest } from '../../common';
import { UpdateAlertRequest } from '../types';
import { createCaseError } from '../../common/error';
import { ENABLE_CASE_CONNECTOR } from '../../../common/constants';
/**
* Throws an error if any of the requests attempt to update a collection style cases' status field.
@ -97,6 +98,22 @@ function throwIfUpdateTypeCollectionToIndividual(
}
}
/**
* Throws an error if any of the requests attempt to update the type of a case.
*/
function throwIfUpdateType(requests: ESCasePatchRequest[]) {
const requestsUpdatingType = requests.filter((req) => req.type !== undefined);
if (requestsUpdatingType.length > 0) {
const ids = requestsUpdatingType.map((req) => req.id);
throw Boom.badRequest(
`Updating the type of a case when sub cases are disabled is not allowed ids: [${ids.join(
', '
)}]`
);
}
}
/**
* Throws an error if any of the requests attempt to update an individual style cases' type field to a collection
* when alerts are attached to the case.
@ -396,6 +413,10 @@ export const update = async ({
return acc;
}, new Map<string, SavedObject<ESCaseAttributes>>());
if (!ENABLE_CASE_CONNECTOR) {
throwIfUpdateType(updateFilterCases);
}
throwIfUpdateStatusOfCollection(updateFilterCases, casesMap);
throwIfUpdateTypeCollectionToIndividual(updateFilterCases, casesMap);
await throwIfInvalidUpdateOfTypeWithAlerts({

View file

@ -36,7 +36,10 @@ import { CommentableCase, createAlertUpdateRequest } from '../../common';
import { CasesClientHandler } from '..';
import { createCaseError } from '../../common/error';
import { CASE_COMMENT_SAVED_OBJECT } from '../../saved_object_types';
import { MAX_GENERATED_ALERTS_PER_SUB_CASE } from '../../../common/constants';
import {
ENABLE_CASE_CONNECTOR,
MAX_GENERATED_ALERTS_PER_SUB_CASE,
} from '../../../common/constants';
async function getSubCase({
caseService,
@ -224,10 +227,14 @@ async function getCombinedCase({
client,
id,
}),
service.getSubCase({
client,
id,
}),
...(ENABLE_CASE_CONNECTOR
? [
service.getSubCase({
client,
id,
}),
]
: [Promise.reject('case connector feature is disabled')]),
]);
if (subCasePromise.status === 'fulfilled') {
@ -287,6 +294,12 @@ export const addComment = async ({
);
if (isCommentRequestTypeGenAlert(comment)) {
if (!ENABLE_CASE_CONNECTOR) {
throw Boom.badRequest(
'Attempting to add a generated alert when case connector feature is disabled'
);
}
return addGeneratedAlerts({
caseId,
comment,

View file

@ -886,7 +886,34 @@ describe('case connector', () => {
});
});
describe('execute', () => {
it('should throw an error when executing the connector', async () => {
expect.assertions(2);
const actionId = 'some-id';
const params: CaseExecutorParams = {
// @ts-expect-error
subAction: 'not-supported',
// @ts-expect-error
subActionParams: {},
};
const executorOptions: CaseActionTypeExecutorOptions = {
actionId,
config: {},
params,
secrets: {},
services,
};
try {
await caseActionType.executor(executorOptions);
} catch (e) {
expect(e).not.toBeNull();
expect(e.message).toBe('[Action][Case] connector not supported');
}
});
// ENABLE_CASE_CONNECTOR: enable these tests after the case connector feature is completed
describe.skip('execute', () => {
it('allows only supported sub-actions', async () => {
expect.assertions(2);
const actionId = 'some-id';

View file

@ -27,6 +27,7 @@ import * as i18n from './translations';
import { GetActionTypeParams, isCommentGeneratedAlert, separator } from '..';
import { nullUser } from '../../common';
import { createCaseError } from '../../common/error';
import { ENABLE_CASE_CONNECTOR } from '../../../common/constants';
const supportedSubActions: string[] = ['create', 'update', 'addComment'];
@ -70,6 +71,12 @@ async function executor(
}: GetActionTypeParams,
execOptions: CaseActionTypeExecutorOptions
): Promise<ActionTypeExecutorResult<CaseExecutorResponse | {}>> {
if (!ENABLE_CASE_CONNECTOR) {
const msg = '[Action][Case] connector not supported';
logger.error(msg);
throw new Error(msg);
}
const { actionId, params, services } = execOptions;
const { subAction, subActionParams } = params;
let data: CaseExecutorResponse | null = null;

View file

@ -10,7 +10,7 @@ import { CoreSetup, CoreStart } from 'src/core/server';
import { SecurityPluginSetup } from '../../security/server';
import { PluginSetupContract as ActionsPluginSetup } from '../../actions/server';
import { APP_ID } from '../common/constants';
import { APP_ID, ENABLE_CASE_CONNECTOR } from '../common/constants';
import { ConfigType } from './config';
import { initCaseApi } from './routes/api';
@ -70,7 +70,6 @@ export class CasePlugin {
core.savedObjects.registerType(caseConfigureSavedObjectType);
core.savedObjects.registerType(caseConnectorMappingsSavedObjectType);
core.savedObjects.registerType(caseSavedObjectType);
core.savedObjects.registerType(subCaseSavedObjectType);
core.savedObjects.registerType(caseUserActionSavedObjectType);
this.log.debug(
@ -111,15 +110,18 @@ export class CasePlugin {
router,
});
registerConnectors({
actionsRegisterType: plugins.actions.registerType,
logger: this.log,
caseService: this.caseService,
caseConfigureService: this.caseConfigureService,
connectorMappingsService: this.connectorMappingsService,
userActionService: this.userActionService,
alertsService: this.alertsService,
});
if (ENABLE_CASE_CONNECTOR) {
core.savedObjects.registerType(subCaseSavedObjectType);
registerConnectors({
actionsRegisterType: plugins.actions.registerType,
logger: this.log,
caseService: this.caseService,
caseConfigureService: this.caseConfigureService,
connectorMappingsService: this.connectorMappingsService,
userActionService: this.userActionService,
alertsService: this.alertsService,
});
}
}
public start(core: CoreStart) {

View file

@ -5,12 +5,12 @@
* 2.0.
*/
import Boom from '@hapi/boom';
import { schema } from '@kbn/config-schema';
import { buildCommentUserActionItem } from '../../../../services/user_actions/helpers';
import { RouteDeps } from '../../types';
import { wrapError } from '../../utils';
import { CASE_COMMENTS_URL } from '../../../../../common/constants';
import { CASE_COMMENTS_URL, ENABLE_CASE_CONNECTOR } from '../../../../../common/constants';
import { AssociationType } from '../../../../../common/api';
export function initDeleteAllCommentsApi({
@ -35,18 +35,23 @@ export function initDeleteAllCommentsApi({
},
async (context, request, response) => {
try {
if (!ENABLE_CASE_CONNECTOR && request.query?.subCaseId !== undefined) {
throw Boom.badRequest(
'The `subCaseId` is not supported when the case connector feature is disabled'
);
}
const client = context.core.savedObjects.client;
// eslint-disable-next-line @typescript-eslint/naming-convention
const { username, full_name, email } = await caseService.getUser({ request });
const deleteDate = new Date().toISOString();
const id = request.query?.subCaseId ?? request.params.case_id;
const subCaseId = request.query?.subCaseId;
const id = subCaseId ?? request.params.case_id;
const comments = await caseService.getCommentsByAssociation({
client,
id,
associationType: request.query?.subCaseId
? AssociationType.subCase
: AssociationType.case,
associationType: subCaseId ? AssociationType.subCase : AssociationType.case,
});
await Promise.all(
@ -66,7 +71,7 @@ export function initDeleteAllCommentsApi({
actionAt: deleteDate,
actionBy: { username, full_name, email },
caseId: request.params.case_id,
subCaseId: request.query?.subCaseId,
subCaseId,
commentId: comment.id,
fields: ['comment'],
})

View file

@ -12,7 +12,7 @@ import { CASE_SAVED_OBJECT, SUB_CASE_SAVED_OBJECT } from '../../../../saved_obje
import { buildCommentUserActionItem } from '../../../../services/user_actions/helpers';
import { RouteDeps } from '../../types';
import { wrapError } from '../../utils';
import { CASE_COMMENT_DETAILS_URL } from '../../../../../common/constants';
import { CASE_COMMENT_DETAILS_URL, ENABLE_CASE_CONNECTOR } from '../../../../../common/constants';
export function initDeleteCommentApi({
caseService,
@ -37,6 +37,12 @@ export function initDeleteCommentApi({
},
async (context, request, response) => {
try {
if (!ENABLE_CASE_CONNECTOR && request.query?.subCaseId !== undefined) {
throw Boom.badRequest(
'The `subCaseId` is not supported when the case connector feature is disabled'
);
}
const client = context.core.savedObjects.client;
// eslint-disable-next-line @typescript-eslint/naming-convention
const { username, full_name, email } = await caseService.getUser({ request });

View file

@ -22,7 +22,7 @@ import {
} from '../../../../../common/api';
import { RouteDeps } from '../../types';
import { escapeHatch, transformComments, wrapError } from '../../utils';
import { CASE_COMMENTS_URL } from '../../../../../common/constants';
import { CASE_COMMENTS_URL, ENABLE_CASE_CONNECTOR } from '../../../../../common/constants';
import { defaultPage, defaultPerPage } from '../..';
const FindQueryParamsRt = rt.partial({
@ -49,6 +49,12 @@ export function initFindCaseCommentsApi({ caseService, router, logger }: RouteDe
fold(throwErrors(Boom.badRequest), identity)
);
if (!ENABLE_CASE_CONNECTOR && query.subCaseId !== undefined) {
throw Boom.badRequest(
'The `subCaseId` is not supported when the case connector feature is disabled'
);
}
const id = query.subCaseId ?? request.params.case_id;
const associationType = query.subCaseId ? AssociationType.subCase : AssociationType.case;
const args = query

View file

@ -5,13 +5,14 @@
* 2.0.
*/
import Boom from '@hapi/boom';
import { schema } from '@kbn/config-schema';
import { SavedObjectsFindResponse } from 'kibana/server';
import { AllCommentsResponseRt, CommentAttributes } from '../../../../../common/api';
import { RouteDeps } from '../../types';
import { flattenCommentSavedObjects, wrapError } from '../../utils';
import { CASE_COMMENTS_URL } from '../../../../../common/constants';
import { CASE_COMMENTS_URL, ENABLE_CASE_CONNECTOR } from '../../../../../common/constants';
import { defaultSortField } from '../../../../common';
export function initGetAllCommentsApi({ caseService, router, logger }: RouteDeps) {
@ -35,6 +36,16 @@ export function initGetAllCommentsApi({ caseService, router, logger }: RouteDeps
const client = context.core.savedObjects.client;
let comments: SavedObjectsFindResponse<CommentAttributes>;
if (
!ENABLE_CASE_CONNECTOR &&
(request.query?.subCaseId !== undefined ||
request.query?.includeSubCaseComments !== undefined)
) {
throw Boom.badRequest(
'The `subCaseId` and `includeSubCaseComments` are not supported when the case connector feature is disabled'
);
}
if (request.query?.subCaseId) {
comments = await caseService.getAllSubCaseComments({
client,

View file

@ -19,7 +19,7 @@ import { CASE_SAVED_OBJECT, SUB_CASE_SAVED_OBJECT } from '../../../../saved_obje
import { buildCommentUserActionItem } from '../../../../services/user_actions/helpers';
import { RouteDeps } from '../../types';
import { escapeHatch, wrapError, decodeCommentRequest } from '../../utils';
import { CASE_COMMENTS_URL } from '../../../../../common/constants';
import { CASE_COMMENTS_URL, ENABLE_CASE_CONNECTOR } from '../../../../../common/constants';
import { CaseServiceSetup } from '../../../../services';
interface CombinedCaseParams {
@ -82,6 +82,12 @@ export function initPatchCommentApi({ caseService, router, userActionService, lo
},
async (context, request, response) => {
try {
if (!ENABLE_CASE_CONNECTOR && request.query?.subCaseId !== undefined) {
throw Boom.badRequest(
'The `subCaseId` is not supported when the case connector feature is disabled'
);
}
const client = context.core.savedObjects.client;
const query = pipe(
CommentPatchRequestRt.decode(request.body),

View file

@ -5,10 +5,11 @@
* 2.0.
*/
import Boom from '@hapi/boom';
import { schema } from '@kbn/config-schema';
import { escapeHatch, wrapError } from '../../utils';
import { RouteDeps } from '../../types';
import { CASE_COMMENTS_URL } from '../../../../../common/constants';
import { CASE_COMMENTS_URL, ENABLE_CASE_CONNECTOR } from '../../../../../common/constants';
import { CommentRequest } from '../../../../../common/api';
export function initPostCommentApi({ router, logger }: RouteDeps) {
@ -28,15 +29,21 @@ export function initPostCommentApi({ router, logger }: RouteDeps) {
},
},
async (context, request, response) => {
if (!context.cases) {
return response.badRequest({ body: 'RouteHandlerContext is not registered for cases' });
}
const casesClient = context.cases.getCasesClient();
const caseId = request.query?.subCaseId ?? request.params.case_id;
const comment = request.body as CommentRequest;
try {
if (!ENABLE_CASE_CONNECTOR && request.query?.subCaseId !== undefined) {
throw Boom.badRequest(
'The `subCaseId` is not supported when the case connector feature is disabled'
);
}
if (!context.cases) {
return response.badRequest({ body: 'RouteHandlerContext is not registered for cases' });
}
const casesClient = context.cases.getCasesClient();
const caseId = request.query?.subCaseId ?? request.params.case_id;
const comment = request.body as CommentRequest;
return response.ok({
body: await casesClient.addComment({ caseId, comment }),
});

View file

@ -11,7 +11,7 @@ import { SavedObjectsClientContract } from 'src/core/server';
import { buildCaseUserActionItem } from '../../../services/user_actions/helpers';
import { RouteDeps } from '../types';
import { wrapError } from '../utils';
import { CASES_URL } from '../../../../common/constants';
import { CASES_URL, ENABLE_CASE_CONNECTOR } from '../../../../common/constants';
import { CaseServiceSetup } from '../../../services';
async function deleteSubCases({
@ -91,7 +91,10 @@ export function initDeleteCasesApi({ caseService, router, userActionService, log
);
}
await deleteSubCases({ caseService, client, caseIds: request.query.ids });
if (ENABLE_CASE_CONNECTOR) {
await deleteSubCases({ caseService, client, caseIds: request.query.ids });
}
// eslint-disable-next-line @typescript-eslint/naming-convention
const { username, full_name, email } = await caseService.getUser({ request });
const deleteDate = new Date().toISOString();
@ -104,7 +107,14 @@ export function initDeleteCasesApi({ caseService, router, userActionService, log
actionAt: deleteDate,
actionBy: { username, full_name, email },
caseId: id,
fields: ['comment', 'description', 'status', 'tags', 'title', 'sub_case'],
fields: [
'comment',
'description',
'status',
'tags',
'title',
...(ENABLE_CASE_CONNECTOR ? ['sub_case'] : []),
],
})
),
});

View file

@ -7,9 +7,10 @@
import { schema } from '@kbn/config-schema';
import Boom from '@hapi/boom';
import { RouteDeps } from '../types';
import { wrapError } from '../utils';
import { CASE_DETAILS_URL } from '../../../../common/constants';
import { CASE_DETAILS_URL, ENABLE_CASE_CONNECTOR } from '../../../../common/constants';
export function initGetCaseApi({ router, logger }: RouteDeps) {
router.get(
@ -27,6 +28,11 @@ export function initGetCaseApi({ router, logger }: RouteDeps) {
},
async (context, request, response) => {
try {
if (!ENABLE_CASE_CONNECTOR && request.query.includeSubCaseComments !== undefined) {
throw Boom.badRequest(
'The `subCaseId` is not supported when the case connector feature is disabled'
);
}
const casesClient = context.cases.getCasesClient();
const id = request.params.case_id;

View file

@ -37,6 +37,7 @@ import { initGetSubCaseApi } from './cases/sub_case/get_sub_case';
import { initPatchSubCasesApi } from './cases/sub_case/patch_sub_cases';
import { initFindSubCasesApi } from './cases/sub_case/find_sub_cases';
import { initDeleteSubCasesApi } from './cases/sub_case/delete_sub_cases';
import { ENABLE_CASE_CONNECTOR } from '../../../common/constants';
/**
* Default page number when interacting with the saved objects API.
@ -56,12 +57,16 @@ export function initCaseApi(deps: RouteDeps) {
initPostCaseApi(deps);
initPushCaseApi(deps);
initGetAllCaseUserActionsApi(deps);
initGetAllSubCaseUserActionsApi(deps);
// Sub cases
initGetSubCaseApi(deps);
initPatchSubCasesApi(deps);
initFindSubCasesApi(deps);
initDeleteSubCasesApi(deps);
if (ENABLE_CASE_CONNECTOR) {
// Sub cases
initGetAllSubCaseUserActionsApi(deps);
initGetSubCaseApi(deps);
initPatchSubCasesApi(deps);
initFindSubCasesApi(deps);
initDeleteSubCasesApi(deps);
}
// Comments
initDeleteCommentApi(deps);
initDeleteAllCommentsApi(deps);

View file

@ -34,6 +34,7 @@ import {
caseTypeField,
CasesFindRequest,
} from '../../common/api';
import { ENABLE_CASE_CONNECTOR } from '../../common/constants';
import { combineFilters, defaultSortField, groupTotalAlertsByID } from '../common';
import { defaultPage, defaultPerPage } from '../routes/api';
import {
@ -282,13 +283,15 @@ export class CaseService implements CaseServiceSetup {
options: caseOptions,
});
const subCasesResp = await this.findSubCasesGroupByCase({
client,
options: subCaseOptions,
ids: cases.saved_objects
.filter((caseInfo) => caseInfo.attributes.type === CaseType.collection)
.map((caseInfo) => caseInfo.id),
});
const subCasesResp = ENABLE_CASE_CONNECTOR
? await this.findSubCasesGroupByCase({
client,
options: subCaseOptions,
ids: cases.saved_objects
.filter((caseInfo) => caseInfo.attributes.type === CaseType.collection)
.map((caseInfo) => caseInfo.id),
})
: { subCasesMap: new Map<string, SubCaseResponse[]>(), page: 0, perPage: 0 };
const casesMap = cases.saved_objects.reduce((accMap, caseInfo) => {
const subCasesForCase = subCasesResp.subCasesMap.get(caseInfo.id);
@ -407,7 +410,7 @@ export class CaseService implements CaseServiceSetup {
let subCasesTotal = 0;
if (subCaseOptions) {
if (ENABLE_CASE_CONNECTOR && subCaseOptions) {
subCasesTotal = await this.findSubCaseStatusStats({
client,
options: subCaseOptions,

View file

@ -5,6 +5,8 @@
* 2.0.
*/
import { ENABLE_CASE_CONNECTOR } from '../../cases/common/constants';
export const APP_ID = 'securitySolution';
export const SERVER_APP_ID = 'siem';
export const APP_NAME = 'Security';
@ -171,7 +173,6 @@ export const ML_GROUP_IDS = [ML_GROUP_ID, LEGACY_ML_GROUP_ID];
/*
Rule notifications options
*/
export const ENABLE_CASE_CONNECTOR = true;
export const NOTIFICATION_SUPPORTED_ACTION_TYPES_IDS = [
'.email',
'.slack',

View file

@ -94,7 +94,26 @@ describe('RuleActionsField', () => {
`);
});
it('if we do NOT have an error on case action creation, we are supporting case connector', () => {
// sub-cases-enabled: remove this once the sub cases and connector feature is completed
// https://github.com/elastic/kibana/issues/94115
it('should not contain the case connector as a supported action', () => {
expect(getSupportedActions(actions, false)).toMatchInlineSnapshot(`
Array [
Object {
"enabled": true,
"enabledInConfig": false,
"enabledInLicense": true,
"id": ".jira",
"minimumLicenseRequired": "gold",
"name": "My Jira",
},
]
`);
});
// sub-cases-enabled: unskip after sub cases and the case connector is supported
// https://github.com/elastic/kibana/issues/94115
it.skip('if we do NOT have an error on case action creation, we are supporting case connector', () => {
expect(getSupportedActions(actions, false)).toMatchInlineSnapshot(`
Array [
Object {

View file

@ -85,7 +85,28 @@ export default ({ getService }: FtrProviderContext): void => {
.expect(404);
});
describe('sub case comments', () => {
it('should return a 400 when attempting to delete all comments when passing the `subCaseId` parameter', async () => {
const { body } = await supertest
.delete(`${CASES_URL}/case-id/comments?subCaseId=value`)
.set('kbn-xsrf', 'true')
.send()
.expect(400);
// make sure the failure is because of the subCaseId
expect(body.message).to.contain('subCaseId');
});
it('should return a 400 when attempting to delete a single comment when passing the `subCaseId` parameter', async () => {
const { body } = await supertest
.delete(`${CASES_URL}/case-id/comments/comment-id?subCaseId=value`)
.set('kbn-xsrf', 'true')
.send()
.expect(400);
// make sure the failure is because of the subCaseId
expect(body.message).to.contain('subCaseId');
});
// ENABLE_CASE_CONNECTOR: once the case connector feature is completed unskip these tests
describe.skip('sub case comments', () => {
let actionID: string;
before(async () => {
actionID = await createCaseAction(supertest);

View file

@ -111,7 +111,18 @@ export default ({ getService }: FtrProviderContext): void => {
.expect(400);
});
describe('sub case comments', () => {
it('should return a 400 when passing the subCaseId parameter', async () => {
const { body } = await supertest
.get(`${CASES_URL}/case-id/comments/_find?search=unique&subCaseId=value`)
.set('kbn-xsrf', 'true')
.send()
.expect(400);
expect(body.message).to.contain('subCaseId');
});
// ENABLE_CASE_CONNECTOR: once the case connector feature is completed unskip these tests
describe.skip('sub case comments', () => {
let actionID: string;
before(async () => {
actionID = await createCaseAction(supertest);

View file

@ -24,13 +24,6 @@ export default ({ getService }: FtrProviderContext): void => {
const es = getService('es');
describe('get_all_comments', () => {
let actionID: string;
before(async () => {
actionID = await createCaseAction(supertest);
});
after(async () => {
await deleteCaseAction(supertest, actionID);
});
afterEach(async () => {
await deleteAllCaseItems(es);
});
@ -63,47 +56,78 @@ export default ({ getService }: FtrProviderContext): void => {
expect(comments.length).to.eql(2);
});
it('should get comments from a case and its sub cases', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
await supertest
.post(`${CASES_URL}/${caseInfo.id}/comments`)
.set('kbn-xsrf', 'true')
.send(postCommentUserReq)
.expect(200);
const { body: comments } = await supertest
.get(`${CASES_URL}/${caseInfo.id}/comments?includeSubCaseComments=true`)
.expect(200);
expect(comments.length).to.eql(2);
expect(comments[0].type).to.eql(CommentType.generatedAlert);
expect(comments[1].type).to.eql(CommentType.user);
});
it('should get comments from a sub cases', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
await supertest
.post(`${CASES_URL}/${caseInfo.subCases![0].id}/comments`)
.set('kbn-xsrf', 'true')
.send(postCommentUserReq)
.expect(200);
const { body: comments } = await supertest
.get(`${CASES_URL}/${caseInfo.id}/comments?subCaseId=${caseInfo.subCases![0].id}`)
.expect(200);
expect(comments.length).to.eql(2);
expect(comments[0].type).to.eql(CommentType.generatedAlert);
expect(comments[1].type).to.eql(CommentType.user);
});
it('should not find any comments for an invalid case id', async () => {
it('should return a 400 when passing the subCaseId parameter', async () => {
const { body } = await supertest
.get(`${CASES_URL}/fake-id/comments`)
.get(`${CASES_URL}/case-id/comments?subCaseId=value`)
.set('kbn-xsrf', 'true')
.send()
.expect(200);
expect(body.length).to.eql(0);
.expect(400);
expect(body.message).to.contain('subCaseId');
});
it('should return a 400 when passing the includeSubCaseComments parameter', async () => {
const { body } = await supertest
.get(`${CASES_URL}/case-id/comments?includeSubCaseComments=true`)
.set('kbn-xsrf', 'true')
.send()
.expect(400);
expect(body.message).to.contain('includeSubCaseComments');
});
// ENABLE_CASE_CONNECTOR: once the case connector feature is completed unskip these tests
describe.skip('sub cases', () => {
let actionID: string;
before(async () => {
actionID = await createCaseAction(supertest);
});
after(async () => {
await deleteCaseAction(supertest, actionID);
});
it('should get comments from a case and its sub cases', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
await supertest
.post(`${CASES_URL}/${caseInfo.id}/comments`)
.set('kbn-xsrf', 'true')
.send(postCommentUserReq)
.expect(200);
const { body: comments } = await supertest
.get(`${CASES_URL}/${caseInfo.id}/comments?includeSubCaseComments=true`)
.expect(200);
expect(comments.length).to.eql(2);
expect(comments[0].type).to.eql(CommentType.generatedAlert);
expect(comments[1].type).to.eql(CommentType.user);
});
it('should get comments from a sub cases', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
await supertest
.post(`${CASES_URL}/${caseInfo.subCases![0].id}/comments`)
.set('kbn-xsrf', 'true')
.send(postCommentUserReq)
.expect(200);
const { body: comments } = await supertest
.get(`${CASES_URL}/${caseInfo.id}/comments?subCaseId=${caseInfo.subCases![0].id}`)
.expect(200);
expect(comments.length).to.eql(2);
expect(comments[0].type).to.eql(CommentType.generatedAlert);
expect(comments[1].type).to.eql(CommentType.user);
});
it('should not find any comments for an invalid case id', async () => {
const { body } = await supertest
.get(`${CASES_URL}/fake-id/comments`)
.set('kbn-xsrf', 'true')
.send()
.expect(200);
expect(body.length).to.eql(0);
});
});
});
};

View file

@ -24,13 +24,6 @@ export default ({ getService }: FtrProviderContext): void => {
const es = getService('es');
describe('get_comment', () => {
let actionID: string;
before(async () => {
actionID = await createCaseAction(supertest);
});
after(async () => {
await deleteCaseAction(supertest, actionID);
});
afterEach(async () => {
await deleteAllCaseItems(es);
});
@ -57,14 +50,6 @@ export default ({ getService }: FtrProviderContext): void => {
expect(comment).to.eql(patchedCase.comments[0]);
});
it('should get a sub case comment', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
const { body: comment }: { body: CommentResponse } = await supertest
.get(`${CASES_URL}/${caseInfo.id}/comments/${caseInfo.comments![0].id}`)
.expect(200);
expect(comment.type).to.be(CommentType.generatedAlert);
});
it('unhappy path - 404s when comment is not there', async () => {
await supertest
.get(`${CASES_URL}/fake-id/comments/fake-comment`)
@ -72,5 +57,23 @@ export default ({ getService }: FtrProviderContext): void => {
.send()
.expect(404);
});
// ENABLE_CASE_CONNECTOR: once the case connector feature is completed unskip these tests
describe.skip('sub cases', () => {
let actionID: string;
before(async () => {
actionID = await createCaseAction(supertest);
});
after(async () => {
await deleteCaseAction(supertest, actionID);
});
it('should get a sub case comment', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
const { body: comment }: { body: CommentResponse } = await supertest
.get(`${CASES_URL}/${caseInfo.id}/comments/${caseInfo.comments![0].id}`)
.expect(200);
expect(comment.type).to.be(CommentType.generatedAlert);
});
});
});
};

View file

@ -39,7 +39,28 @@ export default ({ getService }: FtrProviderContext): void => {
await deleteCasesUserActions(es);
});
describe('sub case comments', () => {
it('should return a 400 when the subCaseId parameter is passed', async () => {
const { body } = await supertest
.patch(`${CASES_URL}/case-id}/comments?subCaseId=value`)
.set('kbn-xsrf', 'true')
.send({
id: 'id',
version: 'version',
type: CommentType.alert,
alertId: 'test-id',
index: 'test-index',
rule: {
id: 'id',
name: 'name',
},
})
.expect(400);
expect(body.message).to.contain('subCaseId');
});
// ENABLE_CASE_CONNECTOR: once the case connector feature is completed unskip these tests
describe.skip('sub case comments', () => {
let actionID: string;
before(async () => {
actionID = await createCaseAction(supertest);

View file

@ -227,7 +227,8 @@ export default ({ getService }: FtrProviderContext): void => {
.expect(400);
});
it('400s when adding an alert to a collection case', async () => {
// ENABLE_CASE_CONNECTOR: once the case connector feature is completed unskip these tests
it.skip('400s when adding an alert to a collection case', async () => {
const { body: postedCase } = await supertest
.post(CASES_URL)
.set('kbn-xsrf', 'true')
@ -376,7 +377,17 @@ export default ({ getService }: FtrProviderContext): void => {
});
});
describe('sub case comments', () => {
it('should return a 400 when passing the subCaseId', async () => {
const { body } = await supertest
.post(`${CASES_URL}/case-id/comments?subCaseId=value`)
.set('kbn-xsrf', 'true')
.send(postCommentUserReq)
.expect(400);
expect(body.message).to.contain('subCaseId');
});
// ENABLE_CASE_CONNECTOR: once the case connector feature is completed unskip these tests
describe.skip('sub case comments', () => {
let actionID: string;
before(async () => {
actionID = await createCaseAction(supertest);

View file

@ -90,7 +90,8 @@ export default ({ getService }: FtrProviderContext): void => {
.expect(404);
});
describe('sub cases', () => {
// ENABLE_CASE_CONNECTOR: once the case connector feature is completed unskip these tests
describe.skip('sub cases', () => {
let actionID: string;
before(async () => {
actionID = await createCaseAction(supertest);

View file

@ -248,7 +248,8 @@ export default ({ getService }: FtrProviderContext): void => {
expect(body.count_in_progress_cases).to.eql(1);
});
describe('stats with sub cases', () => {
// ENABLE_CASE_CONNECTOR: once the case connector feature is completed unskip these tests
describe.skip('stats with sub cases', () => {
let collection: CreateSubCaseResp;
let actionID: string;
before(async () => {

View file

@ -43,6 +43,16 @@ export default ({ getService }: FtrProviderContext): void => {
expect(data).to.eql(postCaseResp(postedCase.id));
});
it('should return a 400 when passing the includeSubCaseComments', async () => {
const { body } = await supertest
.get(`${CASES_URL}/case-id?includeSubCaseComments=true`)
.set('kbn-xsrf', 'true')
.send()
.expect(400);
expect(body.message).to.contain('subCaseId');
});
it('unhappy path - 404s when case is not there', async () => {
await supertest.get(`${CASES_URL}/fake-id`).set('kbn-xsrf', 'true').send().expect(404);
});

View file

@ -134,7 +134,8 @@ export default ({ getService }: FtrProviderContext): void => {
.expect(404);
});
it('should 400 and not allow converting a collection back to an individual case', async () => {
// ENABLE_CASE_CONNECTOR: once the case connector feature is completed unskip these tests
it.skip('should 400 and not allow converting a collection back to an individual case', async () => {
const { body: postedCase } = await supertest
.post(CASES_URL)
.set('kbn-xsrf', 'true')
@ -156,7 +157,8 @@ export default ({ getService }: FtrProviderContext): void => {
.expect(400);
});
it('should allow converting an individual case to a collection when it does not have alerts', async () => {
// ENABLE_CASE_CONNECTOR: once the case connector feature is completed unskip these tests
it.skip('should allow converting an individual case to a collection when it does not have alerts', async () => {
const { body: postedCase } = await supertest
.post(CASES_URL)
.set('kbn-xsrf', 'true')
@ -212,7 +214,30 @@ export default ({ getService }: FtrProviderContext): void => {
.expect(400);
});
it("should 400 when attempting to update a collection case's status", async () => {
it('should 400 when attempting to update the case type when the case connector feature is disabled', 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,
type: CaseType.collection,
},
],
})
.expect(400);
});
// ENABLE_CASE_CONNECTOR: once the case connector feature is completed unskip these tests
it.skip("should 400 when attempting to update a collection case's status", async () => {
const { body: postedCase } = await supertest
.post(CASES_URL)
.set('kbn-xsrf', 'true')

View file

@ -234,7 +234,8 @@ export default ({ getService }: FtrProviderContext): void => {
expect(body.status).to.eql('closed');
});
it('should push a collection case but not close it when closure_type: close-by-pushing', async () => {
// ENABLE_CASE_CONNECTOR: once the case connector feature is completed unskip these tests
it.skip('should push a collection case but not close it when closure_type: close-by-pushing', async () => {
const { body: connector } = await supertest
.post('/api/actions/action')
.set('kbn-xsrf', 'true')

View file

@ -26,75 +26,87 @@ export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
const es = getService('es');
describe('delete_sub_cases', () => {
let actionID: string;
before(async () => {
actionID = await createCaseAction(supertest);
});
after(async () => {
await deleteCaseAction(supertest, actionID);
});
afterEach(async () => {
await deleteAllCaseItems(es);
});
it('should delete a sub case', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
expect(caseInfo.subCases![0].id).to.not.eql(undefined);
const { body: subCase } = await supertest
.get(getSubCaseDetailsUrl(caseInfo.id, caseInfo.subCases![0].id))
.send()
.expect(200);
expect(subCase.id).to.not.eql(undefined);
const { body } = await supertest
.delete(`${SUB_CASES_PATCH_DEL_URL}?ids=["${subCase.id}"]`)
.set('kbn-xsrf', 'true')
.send()
.expect(204);
expect(body).to.eql({});
// ENABLE_CASE_CONNECTOR: remove this outer describe once the case connector feature is completed
describe('delete_sub_cases disabled routes', () => {
it('should return a 404 when attempting to access the route and the case connector feature is disabled', async () => {
await supertest
.get(getSubCaseDetailsUrl(caseInfo.id, caseInfo.subCases![0].id))
.delete(`${SUB_CASES_PATCH_DEL_URL}?ids=["sub-case-id"]`)
.set('kbn-xsrf', 'true')
.send()
.expect(404);
});
it(`should delete a sub case's comments when that case gets deleted`, async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
expect(caseInfo.subCases![0].id).to.not.eql(undefined);
// ENABLE_CASE_CONNECTOR: once the case connector feature is completed unskip these tests
describe.skip('delete_sub_cases', () => {
let actionID: string;
before(async () => {
actionID = await createCaseAction(supertest);
});
after(async () => {
await deleteCaseAction(supertest, actionID);
});
afterEach(async () => {
await deleteAllCaseItems(es);
});
// there should be two comments on the sub case now
const { body: patchedCaseWithSubCase }: { body: CaseResponse } = await supertest
.post(`${CASES_URL}/${caseInfo.id}/comments`)
.set('kbn-xsrf', 'true')
.query({ subCaseId: caseInfo.subCases![0].id })
.send(postCommentUserReq)
.expect(200);
it('should delete a sub case', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
expect(caseInfo.subCases![0].id).to.not.eql(undefined);
const subCaseCommentUrl = `${CASES_URL}/${patchedCaseWithSubCase.id}/comments/${
patchedCaseWithSubCase.comments![1].id
}`;
// make sure we can get the second comment
await supertest.get(subCaseCommentUrl).set('kbn-xsrf', 'true').send().expect(200);
const { body: subCase } = await supertest
.get(getSubCaseDetailsUrl(caseInfo.id, caseInfo.subCases![0].id))
.send()
.expect(200);
await supertest
.delete(`${SUB_CASES_PATCH_DEL_URL}?ids=["${patchedCaseWithSubCase.subCases![0].id}"]`)
.set('kbn-xsrf', 'true')
.send()
.expect(204);
expect(subCase.id).to.not.eql(undefined);
await supertest.get(subCaseCommentUrl).set('kbn-xsrf', 'true').send().expect(404);
});
const { body } = await supertest
.delete(`${SUB_CASES_PATCH_DEL_URL}?ids=["${subCase.id}"]`)
.set('kbn-xsrf', 'true')
.send()
.expect(204);
it('unhappy path - 404s when sub case id is invalid', async () => {
await supertest
.delete(`${SUB_CASES_PATCH_DEL_URL}?ids=["fake-id"]`)
.set('kbn-xsrf', 'true')
.send()
.expect(404);
expect(body).to.eql({});
await supertest
.get(getSubCaseDetailsUrl(caseInfo.id, caseInfo.subCases![0].id))
.send()
.expect(404);
});
it(`should delete a sub case's comments when that case gets deleted`, async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
expect(caseInfo.subCases![0].id).to.not.eql(undefined);
// there should be two comments on the sub case now
const { body: patchedCaseWithSubCase }: { body: CaseResponse } = await supertest
.post(`${CASES_URL}/${caseInfo.id}/comments`)
.set('kbn-xsrf', 'true')
.query({ subCaseId: caseInfo.subCases![0].id })
.send(postCommentUserReq)
.expect(200);
const subCaseCommentUrl = `${CASES_URL}/${patchedCaseWithSubCase.id}/comments/${
patchedCaseWithSubCase.comments![1].id
}`;
// make sure we can get the second comment
await supertest.get(subCaseCommentUrl).set('kbn-xsrf', 'true').send().expect(200);
await supertest
.delete(`${SUB_CASES_PATCH_DEL_URL}?ids=["${patchedCaseWithSubCase.subCases![0].id}"]`)
.set('kbn-xsrf', 'true')
.send()
.expect(204);
await supertest.get(subCaseCommentUrl).set('kbn-xsrf', 'true').send().expect(404);
});
it('unhappy path - 404s when sub case id is invalid', async () => {
await supertest
.delete(`${SUB_CASES_PATCH_DEL_URL}?ids=["fake-id"]`)
.set('kbn-xsrf', 'true')
.send()
.expect(404);
});
});
});
}

View file

@ -29,226 +29,234 @@ export default ({ getService }: FtrProviderContext): void => {
const supertest = getService('supertest');
const es = getService('es');
describe('find_sub_cases', () => {
let actionID: string;
before(async () => {
actionID = await createCaseAction(supertest);
});
after(async () => {
await deleteCaseAction(supertest, actionID);
});
afterEach(async () => {
await deleteAllCaseItems(es);
// ENABLE_CASE_CONNECTOR: remove this outer describe once the case connector feature is completed
describe('find_sub_cases disabled route', () => {
it('should return a 404 when attempting to access the route and the case connector feature is disabled', async () => {
await supertest.get(`${getSubCasesUrl('case-id')}/_find`).expect(404);
});
it('should not find any sub cases when none exist', async () => {
const { body: caseResp }: { body: CaseResponse } = await supertest
.post(CASES_URL)
.set('kbn-xsrf', 'true')
.send(postCollectionReq)
.expect(200);
const { body: findSubCases } = await supertest
.get(`${getSubCasesUrl(caseResp.id)}/_find`)
.expect(200);
expect(findSubCases).to.eql({
page: 1,
per_page: 20,
total: 0,
subCases: [],
count_open_cases: 0,
count_closed_cases: 0,
count_in_progress_cases: 0,
// ENABLE_CASE_CONNECTOR: once the case connector feature is completed unskip these tests
describe.skip('find_sub_cases', () => {
let actionID: string;
before(async () => {
actionID = await createCaseAction(supertest);
});
});
it('should return a sub cases with comment stats', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
const { body }: { body: SubCasesFindResponse } = await supertest
.get(`${getSubCasesUrl(caseInfo.id)}/_find`)
.expect(200);
expect(body).to.eql({
...findSubCasesResp,
total: 1,
// find should not return the comments themselves only the stats
subCases: [{ ...caseInfo.subCases![0], comments: [], totalComment: 1, totalAlerts: 2 }],
count_open_cases: 1,
after(async () => {
await deleteCaseAction(supertest, actionID);
});
});
it('should return multiple sub cases', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
const subCase2Resp = await createSubCase({ supertest, caseID: caseInfo.id, actionID });
const { body }: { body: SubCasesFindResponse } = await supertest
.get(`${getSubCasesUrl(caseInfo.id)}/_find`)
.expect(200);
expect(body).to.eql({
...findSubCasesResp,
total: 2,
// find should not return the comments themselves only the stats
subCases: [
{
// there should only be 1 closed sub case
...subCase2Resp.modifiedSubCases![0],
comments: [],
totalComment: 1,
totalAlerts: 2,
status: CaseStatuses.closed,
},
{
...subCase2Resp.newSubCaseInfo.subCases![0],
comments: [],
totalComment: 1,
totalAlerts: 2,
},
],
count_open_cases: 1,
count_closed_cases: 1,
});
});
it('should only return open when filtering for open', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
// this will result in one closed case and one open
await createSubCase({ supertest, caseID: caseInfo.id, actionID });
const { body }: { body: SubCasesFindResponse } = await supertest
.get(`${getSubCasesUrl(caseInfo.id)}/_find?status=${CaseStatuses.open}`)
.expect(200);
expect(body.total).to.be(1);
expect(body.subCases[0].status).to.be(CaseStatuses.open);
expect(body.count_closed_cases).to.be(1);
expect(body.count_open_cases).to.be(1);
expect(body.count_in_progress_cases).to.be(0);
});
it('should only return closed when filtering for closed', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
// this will result in one closed case and one open
await createSubCase({ supertest, caseID: caseInfo.id, actionID });
const { body }: { body: SubCasesFindResponse } = await supertest
.get(`${getSubCasesUrl(caseInfo.id)}/_find?status=${CaseStatuses.closed}`)
.expect(200);
expect(body.total).to.be(1);
expect(body.subCases[0].status).to.be(CaseStatuses.closed);
expect(body.count_closed_cases).to.be(1);
expect(body.count_open_cases).to.be(1);
expect(body.count_in_progress_cases).to.be(0);
});
it('should only return in progress when filtering for in progress', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
// this will result in one closed case and one open
const { newSubCaseInfo: secondSub } = await createSubCase({
supertest,
caseID: caseInfo.id,
actionID,
afterEach(async () => {
await deleteAllCaseItems(es);
});
await setStatus({
supertest,
cases: [
{
id: secondSub.subCases![0].id,
version: secondSub.subCases![0].version,
status: CaseStatuses['in-progress'],
},
],
type: 'sub_case',
it('should not find any sub cases when none exist', async () => {
const { body: caseResp }: { body: CaseResponse } = await supertest
.post(CASES_URL)
.set('kbn-xsrf', 'true')
.send(postCollectionReq)
.expect(200);
const { body: findSubCases } = await supertest
.get(`${getSubCasesUrl(caseResp.id)}/_find`)
.expect(200);
expect(findSubCases).to.eql({
page: 1,
per_page: 20,
total: 0,
subCases: [],
count_open_cases: 0,
count_closed_cases: 0,
count_in_progress_cases: 0,
});
});
const { body }: { body: SubCasesFindResponse } = await supertest
.get(`${getSubCasesUrl(caseInfo.id)}/_find?status=${CaseStatuses['in-progress']}`)
.expect(200);
it('should return a sub cases with comment stats', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
expect(body.total).to.be(1);
expect(body.subCases[0].status).to.be(CaseStatuses['in-progress']);
expect(body.count_closed_cases).to.be(1);
expect(body.count_open_cases).to.be(0);
expect(body.count_in_progress_cases).to.be(1);
});
const { body }: { body: SubCasesFindResponse } = await supertest
.get(`${getSubCasesUrl(caseInfo.id)}/_find`)
.expect(200);
it('should sort on createdAt field in descending order', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
// this will result in one closed case and one open
await createSubCase({
supertest,
caseID: caseInfo.id,
actionID,
expect(body).to.eql({
...findSubCasesResp,
total: 1,
// find should not return the comments themselves only the stats
subCases: [{ ...caseInfo.subCases![0], comments: [], totalComment: 1, totalAlerts: 2 }],
count_open_cases: 1,
});
});
const { body }: { body: SubCasesFindResponse } = await supertest
.get(`${getSubCasesUrl(caseInfo.id)}/_find?sortField=createdAt&sortOrder=desc`)
.expect(200);
it('should return multiple sub cases', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
const subCase2Resp = await createSubCase({ supertest, caseID: caseInfo.id, actionID });
expect(body.total).to.be(2);
expect(body.subCases[0].status).to.be(CaseStatuses.open);
expect(body.subCases[1].status).to.be(CaseStatuses.closed);
expect(body.count_closed_cases).to.be(1);
expect(body.count_open_cases).to.be(1);
expect(body.count_in_progress_cases).to.be(0);
});
const { body }: { body: SubCasesFindResponse } = await supertest
.get(`${getSubCasesUrl(caseInfo.id)}/_find`)
.expect(200);
it('should sort on createdAt field in ascending order', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
// this will result in one closed case and one open
await createSubCase({
supertest,
caseID: caseInfo.id,
actionID,
expect(body).to.eql({
...findSubCasesResp,
total: 2,
// find should not return the comments themselves only the stats
subCases: [
{
// there should only be 1 closed sub case
...subCase2Resp.modifiedSubCases![0],
comments: [],
totalComment: 1,
totalAlerts: 2,
status: CaseStatuses.closed,
},
{
...subCase2Resp.newSubCaseInfo.subCases![0],
comments: [],
totalComment: 1,
totalAlerts: 2,
},
],
count_open_cases: 1,
count_closed_cases: 1,
});
});
const { body }: { body: SubCasesFindResponse } = await supertest
.get(`${getSubCasesUrl(caseInfo.id)}/_find?sortField=createdAt&sortOrder=asc`)
.expect(200);
it('should only return open when filtering for open', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
// this will result in one closed case and one open
await createSubCase({ supertest, caseID: caseInfo.id, actionID });
expect(body.total).to.be(2);
expect(body.subCases[0].status).to.be(CaseStatuses.closed);
expect(body.subCases[1].status).to.be(CaseStatuses.open);
expect(body.count_closed_cases).to.be(1);
expect(body.count_open_cases).to.be(1);
expect(body.count_in_progress_cases).to.be(0);
});
const { body }: { body: SubCasesFindResponse } = await supertest
.get(`${getSubCasesUrl(caseInfo.id)}/_find?status=${CaseStatuses.open}`)
.expect(200);
it('should sort on updatedAt field in ascending order', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
// this will result in one closed case and one open
const { newSubCaseInfo: secondSub } = await createSubCase({
supertest,
caseID: caseInfo.id,
actionID,
expect(body.total).to.be(1);
expect(body.subCases[0].status).to.be(CaseStatuses.open);
expect(body.count_closed_cases).to.be(1);
expect(body.count_open_cases).to.be(1);
expect(body.count_in_progress_cases).to.be(0);
});
await setStatus({
supertest,
cases: [
{
id: secondSub.subCases![0].id,
version: secondSub.subCases![0].version,
status: CaseStatuses['in-progress'],
},
],
type: 'sub_case',
it('should only return closed when filtering for closed', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
// this will result in one closed case and one open
await createSubCase({ supertest, caseID: caseInfo.id, actionID });
const { body }: { body: SubCasesFindResponse } = await supertest
.get(`${getSubCasesUrl(caseInfo.id)}/_find?status=${CaseStatuses.closed}`)
.expect(200);
expect(body.total).to.be(1);
expect(body.subCases[0].status).to.be(CaseStatuses.closed);
expect(body.count_closed_cases).to.be(1);
expect(body.count_open_cases).to.be(1);
expect(body.count_in_progress_cases).to.be(0);
});
const { body }: { body: SubCasesFindResponse } = await supertest
.get(`${getSubCasesUrl(caseInfo.id)}/_find?sortField=updatedAt&sortOrder=asc`)
.expect(200);
it('should only return in progress when filtering for in progress', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
// this will result in one closed case and one open
const { newSubCaseInfo: secondSub } = await createSubCase({
supertest,
caseID: caseInfo.id,
actionID,
});
expect(body.total).to.be(2);
expect(body.subCases[0].status).to.be(CaseStatuses.closed);
expect(body.subCases[1].status).to.be(CaseStatuses['in-progress']);
expect(body.count_closed_cases).to.be(1);
expect(body.count_open_cases).to.be(0);
expect(body.count_in_progress_cases).to.be(1);
await setStatus({
supertest,
cases: [
{
id: secondSub.subCases![0].id,
version: secondSub.subCases![0].version,
status: CaseStatuses['in-progress'],
},
],
type: 'sub_case',
});
const { body }: { body: SubCasesFindResponse } = await supertest
.get(`${getSubCasesUrl(caseInfo.id)}/_find?status=${CaseStatuses['in-progress']}`)
.expect(200);
expect(body.total).to.be(1);
expect(body.subCases[0].status).to.be(CaseStatuses['in-progress']);
expect(body.count_closed_cases).to.be(1);
expect(body.count_open_cases).to.be(0);
expect(body.count_in_progress_cases).to.be(1);
});
it('should sort on createdAt field in descending order', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
// this will result in one closed case and one open
await createSubCase({
supertest,
caseID: caseInfo.id,
actionID,
});
const { body }: { body: SubCasesFindResponse } = await supertest
.get(`${getSubCasesUrl(caseInfo.id)}/_find?sortField=createdAt&sortOrder=desc`)
.expect(200);
expect(body.total).to.be(2);
expect(body.subCases[0].status).to.be(CaseStatuses.open);
expect(body.subCases[1].status).to.be(CaseStatuses.closed);
expect(body.count_closed_cases).to.be(1);
expect(body.count_open_cases).to.be(1);
expect(body.count_in_progress_cases).to.be(0);
});
it('should sort on createdAt field in ascending order', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
// this will result in one closed case and one open
await createSubCase({
supertest,
caseID: caseInfo.id,
actionID,
});
const { body }: { body: SubCasesFindResponse } = await supertest
.get(`${getSubCasesUrl(caseInfo.id)}/_find?sortField=createdAt&sortOrder=asc`)
.expect(200);
expect(body.total).to.be(2);
expect(body.subCases[0].status).to.be(CaseStatuses.closed);
expect(body.subCases[1].status).to.be(CaseStatuses.open);
expect(body.count_closed_cases).to.be(1);
expect(body.count_open_cases).to.be(1);
expect(body.count_in_progress_cases).to.be(0);
});
it('should sort on updatedAt field in ascending order', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
// this will result in one closed case and one open
const { newSubCaseInfo: secondSub } = await createSubCase({
supertest,
caseID: caseInfo.id,
actionID,
});
await setStatus({
supertest,
cases: [
{
id: secondSub.subCases![0].id,
version: secondSub.subCases![0].version,
status: CaseStatuses['in-progress'],
},
],
type: 'sub_case',
});
const { body }: { body: SubCasesFindResponse } = await supertest
.get(`${getSubCasesUrl(caseInfo.id)}/_find?sortField=updatedAt&sortOrder=asc`)
.expect(200);
expect(body.total).to.be(2);
expect(body.subCases[0].status).to.be(CaseStatuses.closed);
expect(body.subCases[1].status).to.be(CaseStatuses['in-progress']);
expect(body.count_closed_cases).to.be(1);
expect(body.count_open_cases).to.be(0);
expect(body.count_in_progress_cases).to.be(1);
});
});
});
};

View file

@ -37,79 +37,87 @@ export default ({ getService }: FtrProviderContext): void => {
const supertest = getService('supertest');
const es = getService('es');
describe('get_sub_case', () => {
let actionID: string;
before(async () => {
actionID = await createCaseAction(supertest);
});
after(async () => {
await deleteCaseAction(supertest, actionID);
});
afterEach(async () => {
await deleteAllCaseItems(es);
// ENABLE_CASE_CONNECTOR: remove the outer describe once the case connector feature is completed
describe('get_sub_case disabled route', () => {
it('should return a 404 when attempting to access the route and the case connector feature is disabled', async () => {
await supertest.get(getSubCaseDetailsUrl('case-id', 'sub-case-id')).expect(404);
});
it('should return a case', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
// ENABLE_CASE_CONNECTOR: once the case connector feature is completed unskip these tests
describe.skip('get_sub_case', () => {
let actionID: string;
before(async () => {
actionID = await createCaseAction(supertest);
});
after(async () => {
await deleteCaseAction(supertest, actionID);
});
afterEach(async () => {
await deleteAllCaseItems(es);
});
const { body }: { body: SubCaseResponse } = await supertest
.get(getSubCaseDetailsUrl(caseInfo.id, caseInfo.subCases![0].id))
.set('kbn-xsrf', 'true')
.send()
.expect(200);
it('should return a case', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
expect(removeServerGeneratedPropertiesFromComments(body.comments)).to.eql(
commentsResp({
comments: [{ comment: defaultCreateSubComment, id: caseInfo.comments![0].id }],
associationType: AssociationType.subCase,
})
);
const { body }: { body: SubCaseResponse } = await supertest
.get(getSubCaseDetailsUrl(caseInfo.id, caseInfo.subCases![0].id))
.set('kbn-xsrf', 'true')
.send()
.expect(200);
expect(removeServerGeneratedPropertiesFromSubCase(body)).to.eql(
subCaseResp({ id: body.id, totalComment: 1, totalAlerts: 2 })
);
});
expect(removeServerGeneratedPropertiesFromComments(body.comments)).to.eql(
commentsResp({
comments: [{ comment: defaultCreateSubComment, id: caseInfo.comments![0].id }],
associationType: AssociationType.subCase,
})
);
it('should return the correct number of alerts with multiple types of alerts', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
expect(removeServerGeneratedPropertiesFromSubCase(body)).to.eql(
subCaseResp({ id: body.id, totalComment: 1, totalAlerts: 2 })
);
});
const { body: singleAlert }: { body: CaseResponse } = await supertest
.post(getCaseCommentsUrl(caseInfo.id))
.query({ subCaseId: caseInfo.subCases![0].id })
.set('kbn-xsrf', 'true')
.send(postCommentAlertReq)
.expect(200);
it('should return the correct number of alerts with multiple types of alerts', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
const { body }: { body: SubCaseResponse } = await supertest
.get(getSubCaseDetailsUrl(caseInfo.id, caseInfo.subCases![0].id))
.set('kbn-xsrf', 'true')
.send()
.expect(200);
const { body: singleAlert }: { body: CaseResponse } = await supertest
.post(getCaseCommentsUrl(caseInfo.id))
.query({ subCaseId: caseInfo.subCases![0].id })
.set('kbn-xsrf', 'true')
.send(postCommentAlertReq)
.expect(200);
expect(removeServerGeneratedPropertiesFromComments(body.comments)).to.eql(
commentsResp({
comments: [
{ comment: defaultCreateSubComment, id: caseInfo.comments![0].id },
{
comment: postCommentAlertReq,
id: singleAlert.comments![1].id,
},
],
associationType: AssociationType.subCase,
})
);
const { body }: { body: SubCaseResponse } = await supertest
.get(getSubCaseDetailsUrl(caseInfo.id, caseInfo.subCases![0].id))
.set('kbn-xsrf', 'true')
.send()
.expect(200);
expect(removeServerGeneratedPropertiesFromSubCase(body)).to.eql(
subCaseResp({ id: body.id, totalComment: 2, totalAlerts: 3 })
);
});
expect(removeServerGeneratedPropertiesFromComments(body.comments)).to.eql(
commentsResp({
comments: [
{ comment: defaultCreateSubComment, id: caseInfo.comments![0].id },
{
comment: postCommentAlertReq,
id: singleAlert.comments![1].id,
},
],
associationType: AssociationType.subCase,
})
);
it('unhappy path - 404s when case is not there', async () => {
await supertest
.get(getSubCaseDetailsUrl('fake-case-id', 'fake-sub-case-id'))
.set('kbn-xsrf', 'true')
.send()
.expect(404);
expect(removeServerGeneratedPropertiesFromSubCase(body)).to.eql(
subCaseResp({ id: body.id, totalComment: 2, totalAlerts: 3 })
);
});
it('unhappy path - 404s when case is not there', async () => {
await supertest
.get(getSubCaseDetailsUrl('fake-case-id', 'fake-sub-case-id'))
.set('kbn-xsrf', 'true')
.send()
.expect(404);
});
});
});
};

View file

@ -36,463 +36,475 @@ export default function ({ getService }: FtrProviderContext) {
const es = getService('es');
const esArchiver = getService('esArchiver');
describe('patch_sub_cases', () => {
let actionID: string;
before(async () => {
actionID = await createCaseAction(supertest);
});
after(async () => {
await deleteCaseAction(supertest, actionID);
});
beforeEach(async () => {
await esArchiver.load('cases/signals/default');
});
afterEach(async () => {
await esArchiver.unload('cases/signals/default');
await deleteAllCaseItems(es);
});
it('should update the status of a sub case', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
await setStatus({
supertest,
cases: [
{
id: caseInfo.subCases![0].id,
version: caseInfo.subCases![0].version,
status: CaseStatuses['in-progress'],
},
],
type: 'sub_case',
});
const { body: subCase }: { body: SubCaseResponse } = await supertest
.get(getSubCaseDetailsUrl(caseInfo.id, caseInfo.subCases![0].id))
.expect(200);
expect(subCase.status).to.eql(CaseStatuses['in-progress']);
});
it('should update the status of one alert attached to a sub case', async () => {
const signalID = '5f2b9ec41f8febb1c06b5d1045aeabb9874733b7617e88a370510f2fb3a41a5d';
const { newSubCaseInfo: caseInfo } = await createSubCase({
supertest,
actionID,
comment: {
alerts: createAlertsString([
{
_id: signalID,
_index: defaultSignalsIndex,
ruleId: 'id',
ruleName: 'name',
},
]),
type: CommentType.generatedAlert,
},
});
await es.indices.refresh({ index: defaultSignalsIndex });
let signals = await getSignalsWithES({ es, indices: defaultSignalsIndex, ids: signalID });
expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be(
CaseStatuses.open
);
await setStatus({
supertest,
cases: [
{
id: caseInfo.subCases![0].id,
version: caseInfo.subCases![0].version,
status: CaseStatuses['in-progress'],
},
],
type: 'sub_case',
});
await es.indices.refresh({ index: defaultSignalsIndex });
signals = await getSignalsWithES({ es, indices: defaultSignalsIndex, ids: signalID });
expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be(
CaseStatuses['in-progress']
);
});
it('should update the status of multiple alerts attached to a sub case', async () => {
const signalID = '5f2b9ec41f8febb1c06b5d1045aeabb9874733b7617e88a370510f2fb3a41a5d';
const signalID2 = '4d0f4b1533e46b66b43bdd0330d23f39f2cf42a7253153270e38d30cce9ff0c6';
const { newSubCaseInfo: caseInfo } = await createSubCase({
supertest,
actionID,
comment: {
alerts: createAlertsString([
{
_id: signalID,
_index: defaultSignalsIndex,
ruleId: 'id',
ruleName: 'name',
},
{
_id: signalID2,
_index: defaultSignalsIndex,
ruleId: 'id',
ruleName: 'name',
},
]),
type: CommentType.generatedAlert,
},
});
await es.indices.refresh({ index: defaultSignalsIndex });
let signals = await getSignalsWithES({
es,
indices: defaultSignalsIndex,
ids: [signalID, signalID2],
});
expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be(
CaseStatuses.open
);
expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source?.signal.status).to.be(
CaseStatuses.open
);
await setStatus({
supertest,
cases: [
{
id: caseInfo.subCases![0].id,
version: caseInfo.subCases![0].version,
status: CaseStatuses['in-progress'],
},
],
type: 'sub_case',
});
await es.indices.refresh({ index: defaultSignalsIndex });
signals = await getSignalsWithES({
es,
indices: defaultSignalsIndex,
ids: [signalID, signalID2],
});
expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be(
CaseStatuses['in-progress']
);
expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source?.signal.status).to.be(
CaseStatuses['in-progress']
);
});
it('should update the status of multiple alerts attached to multiple sub cases in one collection', async () => {
const signalID = '5f2b9ec41f8febb1c06b5d1045aeabb9874733b7617e88a370510f2fb3a41a5d';
const signalID2 = '4d0f4b1533e46b66b43bdd0330d23f39f2cf42a7253153270e38d30cce9ff0c6';
const { newSubCaseInfo: initialCaseInfo } = await createSubCase({
supertest,
actionID,
caseInfo: {
...postCollectionReq,
settings: {
syncAlerts: false,
},
},
comment: {
alerts: createAlertsString([
{
_id: signalID,
_index: defaultSignalsIndex,
ruleId: 'id',
ruleName: 'name',
},
]),
type: CommentType.generatedAlert,
},
});
// This will close the first sub case and create a new one
const { newSubCaseInfo: collectionWithSecondSub } = await createSubCase({
supertest,
actionID,
caseID: initialCaseInfo.id,
comment: {
alerts: createAlertsString([
{
_id: signalID2,
_index: defaultSignalsIndex,
ruleId: 'id',
ruleName: 'name',
},
]),
type: CommentType.generatedAlert,
},
});
await es.indices.refresh({ index: defaultSignalsIndex });
let signals = await getSignalsWithES({
es,
indices: defaultSignalsIndex,
ids: [signalID, signalID2],
});
// There should be no change in their status since syncing is disabled
expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be(
CaseStatuses.open
);
expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source?.signal.status).to.be(
CaseStatuses.open
);
await setStatus({
supertest,
cases: [
{
id: collectionWithSecondSub.subCases![0].id,
version: collectionWithSecondSub.subCases![0].version,
status: CaseStatuses['in-progress'],
},
],
type: 'sub_case',
});
await es.indices.refresh({ index: defaultSignalsIndex });
signals = await getSignalsWithES({
es,
indices: defaultSignalsIndex,
ids: [signalID, signalID2],
});
// There still should be no change in their status since syncing is disabled
expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be(
CaseStatuses.open
);
expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source?.signal.status).to.be(
CaseStatuses.open
);
// Turn sync alerts on
// ENABLE_CASE_CONNECTOR: remove the outer describe once the case connector feature is completed
describe('patch_sub_cases disabled route', () => {
it('should return a 404 when attempting to access the route and the case connector feature is disabled', async () => {
await supertest
.patch(CASES_URL)
.patch(SUB_CASES_PATCH_DEL_URL)
.set('kbn-xsrf', 'true')
.send({
cases: [
{
id: collectionWithSecondSub.id,
version: collectionWithSecondSub.version,
settings: { syncAlerts: true },
},
],
})
.expect(200);
await es.indices.refresh({ index: defaultSignalsIndex });
signals = await getSignalsWithES({
es,
indices: defaultSignalsIndex,
ids: [signalID, signalID2],
});
expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be(
CaseStatuses.closed
);
expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source?.signal.status).to.be(
CaseStatuses['in-progress']
);
.send({ subCases: [] })
.expect(404);
});
it('should update the status of alerts attached to a case and sub case when sync settings is turned on', async () => {
const signalID = '5f2b9ec41f8febb1c06b5d1045aeabb9874733b7617e88a370510f2fb3a41a5d';
const signalID2 = '4d0f4b1533e46b66b43bdd0330d23f39f2cf42a7253153270e38d30cce9ff0c6';
const { body: individualCase } = await supertest
.post(CASES_URL)
.set('kbn-xsrf', 'true')
.send({
...postCaseReq,
settings: {
syncAlerts: false,
},
});
const { newSubCaseInfo: caseInfo } = await createSubCase({
supertest,
actionID,
caseInfo: {
...postCollectionReq,
settings: {
syncAlerts: false,
},
},
comment: {
alerts: createAlertsString([
{
_id: signalID,
_index: defaultSignalsIndex,
ruleId: 'id',
ruleName: 'name',
},
]),
type: CommentType.generatedAlert,
},
// ENABLE_CASE_CONNECTOR: once the case connector feature is completed unskip these tests
describe.skip('patch_sub_cases', () => {
let actionID: string;
before(async () => {
actionID = await createCaseAction(supertest);
});
after(async () => {
await deleteCaseAction(supertest, actionID);
});
beforeEach(async () => {
await esArchiver.load('cases/signals/default');
});
afterEach(async () => {
await esArchiver.unload('cases/signals/default');
await deleteAllCaseItems(es);
});
const { body: updatedIndWithComment } = await supertest
.post(`${CASES_URL}/${individualCase.id}/comments`)
.set('kbn-xsrf', 'true')
.send({
alertId: signalID2,
index: defaultSignalsIndex,
rule: { id: 'test-rule-id', name: 'test-index-id' },
type: CommentType.alert,
})
.expect(200);
it('should update the status of a sub case', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
await es.indices.refresh({ index: defaultSignalsIndex });
let signals = await getSignalsWithES({
es,
indices: defaultSignalsIndex,
ids: [signalID, signalID2],
});
// There should be no change in their status since syncing is disabled
expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be(
CaseStatuses.open
);
expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source?.signal.status).to.be(
CaseStatuses.open
);
await setStatus({
supertest,
cases: [
{
id: caseInfo.subCases![0].id,
version: caseInfo.subCases![0].version,
status: CaseStatuses['in-progress'],
},
],
type: 'sub_case',
});
const updatedIndWithStatus = (
await setStatus({
supertest,
cases: [
{
id: updatedIndWithComment.id,
version: updatedIndWithComment.version,
status: CaseStatuses.closed,
id: caseInfo.subCases![0].id,
version: caseInfo.subCases![0].version,
status: CaseStatuses['in-progress'],
},
],
type: 'case',
})
)[0]; // there should only be a single entry in the response
type: 'sub_case',
});
const { body: subCase }: { body: SubCaseResponse } = await supertest
.get(getSubCaseDetailsUrl(caseInfo.id, caseInfo.subCases![0].id))
.expect(200);
await es.indices.refresh({ index: defaultSignalsIndex });
signals = await getSignalsWithES({
es,
indices: defaultSignalsIndex,
ids: [signalID, signalID2],
expect(subCase.status).to.eql(CaseStatuses['in-progress']);
});
// There should still be no change in their status since syncing is disabled
expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be(
CaseStatuses.open
);
expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source?.signal.status).to.be(
CaseStatuses.open
);
it('should update the status of one alert attached to a sub case', async () => {
const signalID = '5f2b9ec41f8febb1c06b5d1045aeabb9874733b7617e88a370510f2fb3a41a5d';
// Turn sync alerts on
await supertest
.patch(CASES_URL)
.set('kbn-xsrf', 'true')
.send({
const { newSubCaseInfo: caseInfo } = await createSubCase({
supertest,
actionID,
comment: {
alerts: createAlertsString([
{
_id: signalID,
_index: defaultSignalsIndex,
ruleId: 'id',
ruleName: 'name',
},
]),
type: CommentType.generatedAlert,
},
});
await es.indices.refresh({ index: defaultSignalsIndex });
let signals = await getSignalsWithES({ es, indices: defaultSignalsIndex, ids: signalID });
expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be(
CaseStatuses.open
);
await setStatus({
supertest,
cases: [
{
id: caseInfo.id,
version: caseInfo.version,
settings: { syncAlerts: true },
},
],
})
.expect(200);
await supertest
.patch(CASES_URL)
.set('kbn-xsrf', 'true')
.send({
cases: [
{
id: updatedIndWithStatus.id,
version: updatedIndWithStatus.version,
settings: { syncAlerts: true },
},
],
})
.expect(200);
await es.indices.refresh({ index: defaultSignalsIndex });
signals = await getSignalsWithES({
es,
indices: defaultSignalsIndex,
ids: [signalID, signalID2],
});
// alerts should be updated now that the
expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be(
CaseStatuses['in-progress']
);
expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source?.signal.status).to.be(
CaseStatuses.closed
);
});
it('404s when sub case id is invalid', async () => {
await supertest
.patch(`${SUB_CASES_PATCH_DEL_URL}`)
.set('kbn-xsrf', 'true')
.send({
subCases: [
{
id: 'fake-id',
version: 'blah',
status: CaseStatuses.open,
},
],
})
.expect(404);
});
it('406s when updating invalid fields for a sub case', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
await supertest
.patch(`${SUB_CASES_PATCH_DEL_URL}`)
.set('kbn-xsrf', 'true')
.send({
subCases: [
{
id: caseInfo.subCases![0].id,
version: caseInfo.subCases![0].version,
type: 'blah',
status: CaseStatuses['in-progress'],
},
],
})
.expect(406);
type: 'sub_case',
});
await es.indices.refresh({ index: defaultSignalsIndex });
signals = await getSignalsWithES({ es, indices: defaultSignalsIndex, ids: signalID });
expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be(
CaseStatuses['in-progress']
);
});
it('should update the status of multiple alerts attached to a sub case', async () => {
const signalID = '5f2b9ec41f8febb1c06b5d1045aeabb9874733b7617e88a370510f2fb3a41a5d';
const signalID2 = '4d0f4b1533e46b66b43bdd0330d23f39f2cf42a7253153270e38d30cce9ff0c6';
const { newSubCaseInfo: caseInfo } = await createSubCase({
supertest,
actionID,
comment: {
alerts: createAlertsString([
{
_id: signalID,
_index: defaultSignalsIndex,
ruleId: 'id',
ruleName: 'name',
},
{
_id: signalID2,
_index: defaultSignalsIndex,
ruleId: 'id',
ruleName: 'name',
},
]),
type: CommentType.generatedAlert,
},
});
await es.indices.refresh({ index: defaultSignalsIndex });
let signals = await getSignalsWithES({
es,
indices: defaultSignalsIndex,
ids: [signalID, signalID2],
});
expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be(
CaseStatuses.open
);
expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source?.signal.status).to.be(
CaseStatuses.open
);
await setStatus({
supertest,
cases: [
{
id: caseInfo.subCases![0].id,
version: caseInfo.subCases![0].version,
status: CaseStatuses['in-progress'],
},
],
type: 'sub_case',
});
await es.indices.refresh({ index: defaultSignalsIndex });
signals = await getSignalsWithES({
es,
indices: defaultSignalsIndex,
ids: [signalID, signalID2],
});
expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be(
CaseStatuses['in-progress']
);
expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source?.signal.status).to.be(
CaseStatuses['in-progress']
);
});
it('should update the status of multiple alerts attached to multiple sub cases in one collection', async () => {
const signalID = '5f2b9ec41f8febb1c06b5d1045aeabb9874733b7617e88a370510f2fb3a41a5d';
const signalID2 = '4d0f4b1533e46b66b43bdd0330d23f39f2cf42a7253153270e38d30cce9ff0c6';
const { newSubCaseInfo: initialCaseInfo } = await createSubCase({
supertest,
actionID,
caseInfo: {
...postCollectionReq,
settings: {
syncAlerts: false,
},
},
comment: {
alerts: createAlertsString([
{
_id: signalID,
_index: defaultSignalsIndex,
ruleId: 'id',
ruleName: 'name',
},
]),
type: CommentType.generatedAlert,
},
});
// This will close the first sub case and create a new one
const { newSubCaseInfo: collectionWithSecondSub } = await createSubCase({
supertest,
actionID,
caseID: initialCaseInfo.id,
comment: {
alerts: createAlertsString([
{
_id: signalID2,
_index: defaultSignalsIndex,
ruleId: 'id',
ruleName: 'name',
},
]),
type: CommentType.generatedAlert,
},
});
await es.indices.refresh({ index: defaultSignalsIndex });
let signals = await getSignalsWithES({
es,
indices: defaultSignalsIndex,
ids: [signalID, signalID2],
});
// There should be no change in their status since syncing is disabled
expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be(
CaseStatuses.open
);
expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source?.signal.status).to.be(
CaseStatuses.open
);
await setStatus({
supertest,
cases: [
{
id: collectionWithSecondSub.subCases![0].id,
version: collectionWithSecondSub.subCases![0].version,
status: CaseStatuses['in-progress'],
},
],
type: 'sub_case',
});
await es.indices.refresh({ index: defaultSignalsIndex });
signals = await getSignalsWithES({
es,
indices: defaultSignalsIndex,
ids: [signalID, signalID2],
});
// There still should be no change in their status since syncing is disabled
expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be(
CaseStatuses.open
);
expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source?.signal.status).to.be(
CaseStatuses.open
);
// Turn sync alerts on
await supertest
.patch(CASES_URL)
.set('kbn-xsrf', 'true')
.send({
cases: [
{
id: collectionWithSecondSub.id,
version: collectionWithSecondSub.version,
settings: { syncAlerts: true },
},
],
})
.expect(200);
await es.indices.refresh({ index: defaultSignalsIndex });
signals = await getSignalsWithES({
es,
indices: defaultSignalsIndex,
ids: [signalID, signalID2],
});
expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be(
CaseStatuses.closed
);
expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source?.signal.status).to.be(
CaseStatuses['in-progress']
);
});
it('should update the status of alerts attached to a case and sub case when sync settings is turned on', async () => {
const signalID = '5f2b9ec41f8febb1c06b5d1045aeabb9874733b7617e88a370510f2fb3a41a5d';
const signalID2 = '4d0f4b1533e46b66b43bdd0330d23f39f2cf42a7253153270e38d30cce9ff0c6';
const { body: individualCase } = await supertest
.post(CASES_URL)
.set('kbn-xsrf', 'true')
.send({
...postCaseReq,
settings: {
syncAlerts: false,
},
});
const { newSubCaseInfo: caseInfo } = await createSubCase({
supertest,
actionID,
caseInfo: {
...postCollectionReq,
settings: {
syncAlerts: false,
},
},
comment: {
alerts: createAlertsString([
{
_id: signalID,
_index: defaultSignalsIndex,
ruleId: 'id',
ruleName: 'name',
},
]),
type: CommentType.generatedAlert,
},
});
const { body: updatedIndWithComment } = await supertest
.post(`${CASES_URL}/${individualCase.id}/comments`)
.set('kbn-xsrf', 'true')
.send({
alertId: signalID2,
index: defaultSignalsIndex,
rule: { id: 'test-rule-id', name: 'test-index-id' },
type: CommentType.alert,
})
.expect(200);
await es.indices.refresh({ index: defaultSignalsIndex });
let signals = await getSignalsWithES({
es,
indices: defaultSignalsIndex,
ids: [signalID, signalID2],
});
// There should be no change in their status since syncing is disabled
expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be(
CaseStatuses.open
);
expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source?.signal.status).to.be(
CaseStatuses.open
);
await setStatus({
supertest,
cases: [
{
id: caseInfo.subCases![0].id,
version: caseInfo.subCases![0].version,
status: CaseStatuses['in-progress'],
},
],
type: 'sub_case',
});
const updatedIndWithStatus = (
await setStatus({
supertest,
cases: [
{
id: updatedIndWithComment.id,
version: updatedIndWithComment.version,
status: CaseStatuses.closed,
},
],
type: 'case',
})
)[0]; // there should only be a single entry in the response
await es.indices.refresh({ index: defaultSignalsIndex });
signals = await getSignalsWithES({
es,
indices: defaultSignalsIndex,
ids: [signalID, signalID2],
});
// There should still be no change in their status since syncing is disabled
expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be(
CaseStatuses.open
);
expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source?.signal.status).to.be(
CaseStatuses.open
);
// Turn sync alerts on
await supertest
.patch(CASES_URL)
.set('kbn-xsrf', 'true')
.send({
cases: [
{
id: caseInfo.id,
version: caseInfo.version,
settings: { syncAlerts: true },
},
],
})
.expect(200);
await supertest
.patch(CASES_URL)
.set('kbn-xsrf', 'true')
.send({
cases: [
{
id: updatedIndWithStatus.id,
version: updatedIndWithStatus.version,
settings: { syncAlerts: true },
},
],
})
.expect(200);
await es.indices.refresh({ index: defaultSignalsIndex });
signals = await getSignalsWithES({
es,
indices: defaultSignalsIndex,
ids: [signalID, signalID2],
});
// alerts should be updated now that the
expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be(
CaseStatuses['in-progress']
);
expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source?.signal.status).to.be(
CaseStatuses.closed
);
});
it('404s when sub case id is invalid', async () => {
await supertest
.patch(`${SUB_CASES_PATCH_DEL_URL}`)
.set('kbn-xsrf', 'true')
.send({
subCases: [
{
id: 'fake-id',
version: 'blah',
status: CaseStatuses.open,
},
],
})
.expect(404);
});
it('406s when updating invalid fields for a sub case', async () => {
const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID });
await supertest
.patch(`${SUB_CASES_PATCH_DEL_URL}`)
.set('kbn-xsrf', 'true')
.send({
subCases: [
{
id: caseInfo.subCases![0].id,
version: caseInfo.subCases![0].version,
type: 'blah',
},
],
})
.expect(406);
});
});
});
}

View file

@ -36,7 +36,20 @@ export default ({ getService }: FtrProviderContext): void => {
describe('case_connector', () => {
let createdActionId = '';
it('should return 200 when creating a case action successfully', async () => {
it('should return 400 when creating a case action', async () => {
await supertest
.post('/api/actions/action')
.set('kbn-xsrf', 'foo')
.send({
name: 'A case connector',
actionTypeId: '.case',
config: {},
})
.expect(400);
});
// ENABLE_CASE_CONNECTOR: once the case connector feature is completed unskip these tests
it.skip('should return 200 when creating a case action successfully', async () => {
const { body: createdAction } = await supertest
.post('/api/actions/action')
.set('kbn-xsrf', 'foo')
@ -70,7 +83,7 @@ export default ({ getService }: FtrProviderContext): void => {
});
});
describe('create', () => {
describe.skip('create', () => {
it('should respond with a 400 Bad Request when creating a case without title', async () => {
const { body: createdAction } = await supertest
.post('/api/actions/action')
@ -500,7 +513,7 @@ export default ({ getService }: FtrProviderContext): void => {
});
});
describe('update', () => {
describe.skip('update', () => {
it('should respond with a 400 Bad Request when updating a case without id', async () => {
const { body: createdAction } = await supertest
.post('/api/actions/action')
@ -624,7 +637,7 @@ export default ({ getService }: FtrProviderContext): void => {
});
});
describe('addComment', () => {
describe.skip('addComment', () => {
it('should respond with a 400 Bad Request when adding a comment to a case without caseId', async () => {
const { body: createdAction } = await supertest
.post('/api/actions/action')

View file

@ -6,7 +6,7 @@
*/
import expect from '@kbn/expect';
import type { estypes } from '@elastic/elasticsearch';
import type { ApiResponse, estypes } from '@elastic/elasticsearch';
import type { KibanaClient } from '@elastic/elasticsearch/api/kibana';
import * as st from 'supertest';
@ -48,7 +48,7 @@ export const getSignalsWithES = async ({
indices: string | string[];
ids: string | string[];
}): Promise<Map<string, Map<string, estypes.Hit<SignalHit>>>> => {
const signals = await es.search<SignalHit>({
const signals: ApiResponse<estypes.SearchResponse<SignalHit>> = await es.search({
index: indices,
body: {
size: 10000,