From a108469ec78a6df4d7628eb94f492b583e7014e6 Mon Sep 17 00:00:00 2001 From: Jonathan Buttner <56361221+jonathan-buttner@users.noreply.github.com> Date: Fri, 19 Feb 2021 10:50:35 -0500 Subject: [PATCH] Allowing deletion of collections (#91926) --- .../server/routes/api/cases/delete_cases.ts | 44 ------------ .../basic/tests/cases/delete_cases.ts | 71 ++++++++++++++++++- 2 files changed, 70 insertions(+), 45 deletions(-) diff --git a/x-pack/plugins/case/server/routes/api/cases/delete_cases.ts b/x-pack/plugins/case/server/routes/api/cases/delete_cases.ts index 263b814df414..497e33d7feb3 100644 --- a/x-pack/plugins/case/server/routes/api/cases/delete_cases.ts +++ b/x-pack/plugins/case/server/routes/api/cases/delete_cases.ts @@ -8,43 +8,12 @@ import { schema } from '@kbn/config-schema'; import { SavedObjectsClientContract } from 'src/core/server'; -import { CaseType } from '../../../../common/api'; import { buildCaseUserActionItem } from '../../../services/user_actions/helpers'; import { RouteDeps } from '../types'; import { wrapError } from '../utils'; import { CASES_URL } from '../../../../common/constants'; import { CaseServiceSetup } from '../../../services'; -async function unremovableCases({ - caseService, - client, - ids, - force, -}: { - caseService: CaseServiceSetup; - client: SavedObjectsClientContract; - ids: string[]; - force: boolean | undefined; -}): Promise { - // if the force flag was included then we can skip checking whether the cases are collections and go ahead - // and delete them - if (force) { - return []; - } - - const cases = await caseService.getCases({ caseIds: ids, client }); - const parentCases = cases.saved_objects.filter( - /** - * getCases will return an array of saved_objects and some can be successful cases where as others - * might have failed to find the ID. If it fails to find it, it will set the error field but not - * the attributes so check that we didn't receive an error. - */ - (caseObj) => !caseObj.error && caseObj.attributes.type === CaseType.collection - ); - - return parentCases.map((parentCase) => parentCase.id); -} - async function deleteSubCases({ caseService, client, @@ -84,25 +53,12 @@ export function initDeleteCasesApi({ caseService, router, userActionService }: R validate: { query: schema.object({ ids: schema.arrayOf(schema.string()), - force: schema.maybe(schema.boolean()), }), }, }, async (context, request, response) => { try { const client = context.core.savedObjects.client; - const unremovable = await unremovableCases({ - caseService, - client, - ids: request.query.ids, - force: request.query.force, - }); - - if (unremovable.length > 0) { - return response.badRequest({ - body: `Case IDs: [${unremovable.join(' ,')}] are not removable`, - }); - } await Promise.all( request.query.ids.map((id) => caseService.deleteCase({ 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 1ea4712e260f..8edc3b0d0811 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 @@ -10,7 +10,17 @@ import { FtrProviderContext } from '../../../common/ftr_provider_context'; import { CASES_URL } from '../../../../../plugins/case/common/constants'; import { postCaseReq, postCommentUserReq } from '../../../common/lib/mock'; -import { deleteCases, deleteCasesUserActions, deleteComments } from '../../../common/lib/utils'; +import { + createCaseAction, + createSubCase, + deleteAllCaseItems, + deleteCaseAction, + deleteCases, + deleteCasesUserActions, + deleteComments, +} from '../../../common/lib/utils'; +import { getSubCaseDetailsUrl } from '../../../../../plugins/case/common/api/helpers'; +import { CollectionWithSubCaseResponse } from '../../../../../plugins/case/common/api'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { @@ -79,5 +89,64 @@ export default ({ getService }: FtrProviderContext): void => { .send() .expect(404); }); + + describe('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 the sub cases when deleting a collection', async () => { + const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID }); + expect(caseInfo.subCase?.id).to.not.eql(undefined); + + const { body } = await supertest + .delete(`${CASES_URL}?ids=["${caseInfo.id}"]`) + .set('kbn-xsrf', 'true') + .send() + .expect(204); + + expect(body).to.eql({}); + await supertest + .get(getSubCaseDetailsUrl(caseInfo.id, caseInfo.subCase!.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.subCase?.id).to.not.eql(undefined); + + // there should be two comments on the sub case now + const { + body: patchedCaseWithSubCase, + }: { body: CollectionWithSubCaseResponse } = await supertest + .post(`${CASES_URL}/${caseInfo.id}/comments`) + .set('kbn-xsrf', 'true') + .query({ subCaseID: caseInfo.subCase!.id }) + .send(postCommentUserReq) + .expect(200); + + const subCaseCommentUrl = `${CASES_URL}/${patchedCaseWithSubCase.id}/comments/${ + patchedCaseWithSubCase.subCase!.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(`${CASES_URL}?ids=["${caseInfo.id}"]`) + .set('kbn-xsrf', 'true') + .send() + .expect(204); + + await supertest.get(subCaseCommentUrl).set('kbn-xsrf', 'true').send().expect(404); + }); + }); }); };