From 07f32d03b30a160511f67a9e59b563cde4200fbb Mon Sep 17 00:00:00 2001 From: Jonathan Buttner <56361221+jonathan-buttner@users.noreply.github.com> Date: Thu, 25 Mar 2021 13:36:25 -0400 Subject: [PATCH] [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 --- x-pack/plugins/cases/common/constants.ts | 9 +- .../cases/server/client/cases/create.ts | 13 +- .../plugins/cases/server/client/cases/get.ts | 32 +- .../plugins/cases/server/client/cases/push.ts | 9 +- .../cases/server/client/cases/update.ts | 21 + .../cases/server/client/comments/add.ts | 23 +- .../server/connectors/case/index.test.ts | 29 +- .../cases/server/connectors/case/index.ts | 7 + x-pack/plugins/cases/server/plugin.ts | 24 +- .../api/cases/comments/delete_all_comments.ts | 19 +- .../api/cases/comments/delete_comment.ts | 8 +- .../api/cases/comments/find_comments.ts | 8 +- .../api/cases/comments/get_all_comment.ts | 13 +- .../api/cases/comments/patch_comment.ts | 8 +- .../routes/api/cases/comments/post_comment.ts | 25 +- .../server/routes/api/cases/delete_cases.ts | 16 +- .../cases/server/routes/api/cases/get_case.ts | 8 +- .../plugins/cases/server/routes/api/index.ts | 17 +- x-pack/plugins/cases/server/services/index.ts | 19 +- .../security_solution/common/constants.ts | 3 +- .../rules/rule_actions_field/index.test.tsx | 21 +- .../tests/cases/comments/delete_comment.ts | 23 +- .../tests/cases/comments/find_comments.ts | 13 +- .../tests/cases/comments/get_all_comments.ts | 114 ++- .../basic/tests/cases/comments/get_comment.ts | 33 +- .../tests/cases/comments/patch_comment.ts | 23 +- .../tests/cases/comments/post_comment.ts | 15 +- .../basic/tests/cases/delete_cases.ts | 3 +- .../basic/tests/cases/find_cases.ts | 3 +- .../basic/tests/cases/get_case.ts | 10 + .../basic/tests/cases/patch_cases.ts | 31 +- .../basic/tests/cases/push_case.ts | 3 +- .../tests/cases/sub_cases/delete_sub_cases.ts | 130 +-- .../tests/cases/sub_cases/find_sub_cases.ts | 402 ++++---- .../tests/cases/sub_cases/get_sub_case.ts | 132 +-- .../tests/cases/sub_cases/patch_sub_cases.ts | 880 +++++++++--------- .../basic/tests/connectors/case.ts | 21 +- .../case_api_integration/common/lib/utils.ts | 4 +- 38 files changed, 1271 insertions(+), 901 deletions(-) diff --git a/x-pack/plugins/cases/common/constants.ts b/x-pack/plugins/cases/common/constants.ts index 1e7cff99a00b..148b81c346b6 100644 --- a/x-pack/plugins/cases/common/constants.ts +++ b/x-pack/plugins/cases/common/constants.ts @@ -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; diff --git a/x-pack/plugins/cases/server/client/cases/create.ts b/x-pack/plugins/cases/server/client/cases/create.ts index 59f968883634..650b9aa81c99 100644 --- a/x-pack/plugins/cases/server/client/cases/create.ts +++ b/x-pack/plugins/cases/server/client/cases/create.ts @@ -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 => { // 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) ); diff --git a/x-pack/plugins/cases/server/client/cases/get.ts b/x-pack/plugins/cases/server/client/cases/get.ts index fa556986ee8d..50725879278e 100644 --- a/x-pack/plugins/cases/server/client/cases/get.ts +++ b/x-pack/plugins/cases/server/client/cases/get.ts @@ -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 => { try { - const [theCase, subCasesForCaseId] = await Promise.all([ - caseService.getCase({ + let theCase: SavedObject; + 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( diff --git a/x-pack/plugins/cases/server/client/cases/push.ts b/x-pack/plugins/cases/server/client/cases/push.ts index 3217178768f8..216ef109534f 100644 --- a/x-pack/plugins/cases/server/client/cases/push.ts +++ b/x-pack/plugins/cases/server/client/cases/push.ts @@ -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) { diff --git a/x-pack/plugins/cases/server/client/cases/update.ts b/x-pack/plugins/cases/server/client/cases/update.ts index ff3c0a62407a..b39bfe6ec4eb 100644 --- a/x-pack/plugins/cases/server/client/cases/update.ts +++ b/x-pack/plugins/cases/server/client/cases/update.ts @@ -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>()); + if (!ENABLE_CASE_CONNECTOR) { + throwIfUpdateType(updateFilterCases); + } + throwIfUpdateStatusOfCollection(updateFilterCases, casesMap); throwIfUpdateTypeCollectionToIndividual(updateFilterCases, casesMap); await throwIfInvalidUpdateOfTypeWithAlerts({ diff --git a/x-pack/plugins/cases/server/client/comments/add.ts b/x-pack/plugins/cases/server/client/comments/add.ts index 45746613dc1d..5a119432b3cc 100644 --- a/x-pack/plugins/cases/server/client/comments/add.ts +++ b/x-pack/plugins/cases/server/client/comments/add.ts @@ -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, diff --git a/x-pack/plugins/cases/server/connectors/case/index.test.ts b/x-pack/plugins/cases/server/connectors/case/index.test.ts index fa2b10a0ccbd..8a025ed0f79b 100644 --- a/x-pack/plugins/cases/server/connectors/case/index.test.ts +++ b/x-pack/plugins/cases/server/connectors/case/index.test.ts @@ -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'; diff --git a/x-pack/plugins/cases/server/connectors/case/index.ts b/x-pack/plugins/cases/server/connectors/case/index.ts index da993faf0ef5..c5eb609e260a 100644 --- a/x-pack/plugins/cases/server/connectors/case/index.ts +++ b/x-pack/plugins/cases/server/connectors/case/index.ts @@ -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> { + 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; diff --git a/x-pack/plugins/cases/server/plugin.ts b/x-pack/plugins/cases/server/plugin.ts index 0c661cc18c21..8b53fd77d98a 100644 --- a/x-pack/plugins/cases/server/plugin.ts +++ b/x-pack/plugins/cases/server/plugin.ts @@ -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) { diff --git a/x-pack/plugins/cases/server/routes/api/cases/comments/delete_all_comments.ts b/x-pack/plugins/cases/server/routes/api/cases/comments/delete_all_comments.ts index fd250b74fff1..7f6cfb224fad 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/comments/delete_all_comments.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/comments/delete_all_comments.ts @@ -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'], }) diff --git a/x-pack/plugins/cases/server/routes/api/cases/comments/delete_comment.ts b/x-pack/plugins/cases/server/routes/api/cases/comments/delete_comment.ts index f1c5fdc2b7cc..f8771f92c417 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/comments/delete_comment.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/comments/delete_comment.ts @@ -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 }); diff --git a/x-pack/plugins/cases/server/routes/api/cases/comments/find_comments.ts b/x-pack/plugins/cases/server/routes/api/cases/comments/find_comments.ts index 57ddd84e8742..9468b2b01fe3 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/comments/find_comments.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/comments/find_comments.ts @@ -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 diff --git a/x-pack/plugins/cases/server/routes/api/cases/comments/get_all_comment.ts b/x-pack/plugins/cases/server/routes/api/cases/comments/get_all_comment.ts index 770efe010974..2699f7a0307f 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/comments/get_all_comment.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/comments/get_all_comment.ts @@ -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; + 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, diff --git a/x-pack/plugins/cases/server/routes/api/cases/comments/patch_comment.ts b/x-pack/plugins/cases/server/routes/api/cases/comments/patch_comment.ts index f5db2dc004a1..519692d2d78a 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/comments/patch_comment.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/comments/patch_comment.ts @@ -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), diff --git a/x-pack/plugins/cases/server/routes/api/cases/comments/post_comment.ts b/x-pack/plugins/cases/server/routes/api/cases/comments/post_comment.ts index 110a16a61001..8658f9ba0aac 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/comments/post_comment.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/comments/post_comment.ts @@ -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 }), }); diff --git a/x-pack/plugins/cases/server/routes/api/cases/delete_cases.ts b/x-pack/plugins/cases/server/routes/api/cases/delete_cases.ts index 5f2a6c67220c..d91859d4e8cb 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/delete_cases.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/delete_cases.ts @@ -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'] : []), + ], }) ), }); diff --git a/x-pack/plugins/cases/server/routes/api/cases/get_case.ts b/x-pack/plugins/cases/server/routes/api/cases/get_case.ts index f464f7e47fe7..e8e35d875f42 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/get_case.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/get_case.ts @@ -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; diff --git a/x-pack/plugins/cases/server/routes/api/index.ts b/x-pack/plugins/cases/server/routes/api/index.ts index 12d1da36077c..c5b7aa85dc33 100644 --- a/x-pack/plugins/cases/server/routes/api/index.ts +++ b/x-pack/plugins/cases/server/routes/api/index.ts @@ -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); diff --git a/x-pack/plugins/cases/server/services/index.ts b/x-pack/plugins/cases/server/services/index.ts index 11ceb48d11e9..7c5f06d48bb0 100644 --- a/x-pack/plugins/cases/server/services/index.ts +++ b/x-pack/plugins/cases/server/services/index.ts @@ -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(), 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, diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 143384d16047..4c62179f9ed5 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -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', diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.test.tsx index 5dbe1f1cef5b..fb71c6c4b035 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.test.tsx @@ -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 { diff --git a/x-pack/test/case_api_integration/basic/tests/cases/comments/delete_comment.ts b/x-pack/test/case_api_integration/basic/tests/cases/comments/delete_comment.ts index 7cb66b6815b9..fece9abd5fa3 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/comments/delete_comment.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/comments/delete_comment.ts @@ -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); diff --git a/x-pack/test/case_api_integration/basic/tests/cases/comments/find_comments.ts b/x-pack/test/case_api_integration/basic/tests/cases/comments/find_comments.ts index 7bbc8e344ee2..44ff1c7ebffe 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/comments/find_comments.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/comments/find_comments.ts @@ -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); diff --git a/x-pack/test/case_api_integration/basic/tests/cases/comments/get_all_comments.ts b/x-pack/test/case_api_integration/basic/tests/cases/comments/get_all_comments.ts index 723c9eba33be..e73614d88ca9 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/comments/get_all_comments.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/comments/get_all_comments.ts @@ -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); + }); }); }); }; diff --git a/x-pack/test/case_api_integration/basic/tests/cases/comments/get_comment.ts b/x-pack/test/case_api_integration/basic/tests/cases/comments/get_comment.ts index 1a1bb727bd42..a74d8f86d225 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/comments/get_comment.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/comments/get_comment.ts @@ -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); + }); + }); }); }; diff --git a/x-pack/test/case_api_integration/basic/tests/cases/comments/patch_comment.ts b/x-pack/test/case_api_integration/basic/tests/cases/comments/patch_comment.ts index bddc620535dd..b59a248ee07e 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/comments/patch_comment.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/comments/patch_comment.ts @@ -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); diff --git a/x-pack/test/case_api_integration/basic/tests/cases/comments/post_comment.ts b/x-pack/test/case_api_integration/basic/tests/cases/comments/post_comment.ts index 5e48e39164e6..c46698a0905b 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/comments/post_comment.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/comments/post_comment.ts @@ -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); diff --git a/x-pack/test/case_api_integration/basic/tests/cases/delete_cases.ts b/x-pack/test/case_api_integration/basic/tests/cases/delete_cases.ts index b5187931a9f0..706fded26328 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/delete_cases.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/delete_cases.ts @@ -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); diff --git a/x-pack/test/case_api_integration/basic/tests/cases/find_cases.ts b/x-pack/test/case_api_integration/basic/tests/cases/find_cases.ts index b808ff4ccdf3..7277c93b7b75 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/find_cases.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/find_cases.ts @@ -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 () => { diff --git a/x-pack/test/case_api_integration/basic/tests/cases/get_case.ts b/x-pack/test/case_api_integration/basic/tests/cases/get_case.ts index fb4ab2c86469..43bd7a616e72 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/get_case.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/get_case.ts @@ -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); }); diff --git a/x-pack/test/case_api_integration/basic/tests/cases/patch_cases.ts b/x-pack/test/case_api_integration/basic/tests/cases/patch_cases.ts index c202111f0e5e..f43b47da19ad 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/patch_cases.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/patch_cases.ts @@ -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') diff --git a/x-pack/test/case_api_integration/basic/tests/cases/push_case.ts b/x-pack/test/case_api_integration/basic/tests/cases/push_case.ts index 2db15eb603f7..47842eeca6f1 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/push_case.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/push_case.ts @@ -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') diff --git a/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/delete_sub_cases.ts b/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/delete_sub_cases.ts index d179120cd3d8..15b3b9311e3c 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/delete_sub_cases.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/delete_sub_cases.ts @@ -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); + }); }); }); } diff --git a/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/find_sub_cases.ts b/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/find_sub_cases.ts index 2c1bd9c7bd88..b7f2094b0acc 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/find_sub_cases.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/find_sub_cases.ts @@ -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); + }); }); }); }; diff --git a/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/get_sub_case.ts b/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/get_sub_case.ts index 440731cd07fe..8d4ffafbf763 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/get_sub_case.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/get_sub_case.ts @@ -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); + }); }); }); }; diff --git a/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/patch_sub_cases.ts b/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/patch_sub_cases.ts index e5cc2489a12e..d993a627d186 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/patch_sub_cases.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/patch_sub_cases.ts @@ -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); + }); }); }); } diff --git a/x-pack/test/case_api_integration/basic/tests/connectors/case.ts b/x-pack/test/case_api_integration/basic/tests/connectors/case.ts index ee4d671f7880..0f9cba4b51f7 100644 --- a/x-pack/test/case_api_integration/basic/tests/connectors/case.ts +++ b/x-pack/test/case_api_integration/basic/tests/connectors/case.ts @@ -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') diff --git a/x-pack/test/case_api_integration/common/lib/utils.ts b/x-pack/test/case_api_integration/common/lib/utils.ts index 6fb108f69ad2..f7ff49727df3 100644 --- a/x-pack/test/case_api_integration/common/lib/utils.ts +++ b/x-pack/test/case_api_integration/common/lib/utils.ts @@ -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>>> => { - const signals = await es.search({ + const signals: ApiResponse> = await es.search({ index: indices, body: { size: 10000,