From a8159178638d40d6675d099a644080c73eb5b679 Mon Sep 17 00:00:00 2001 From: adcoelho Date: Wed, 5 Apr 2023 09:59:42 +0200 Subject: [PATCH] Added api tests for deleteFileAttachments. Added FileDeleteButtonIcon tests. Added useDeleteFileAttachment tests. --- .../files/file_delete_button_icon.test.tsx | 61 ++++++++ .../cases/public/containers/__mocks__/api.ts | 10 ++ .../cases/public/containers/api.test.tsx | 27 ++++ .../use_delete_file_attachment.test.tsx | 134 ++++++++++++++++++ .../containers/use_delete_file_attachment.tsx | 4 +- 5 files changed, 234 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugins/cases/public/components/files/file_delete_button_icon.test.tsx create mode 100644 x-pack/plugins/cases/public/containers/use_delete_file_attachment.test.tsx diff --git a/x-pack/plugins/cases/public/components/files/file_delete_button_icon.test.tsx b/x-pack/plugins/cases/public/components/files/file_delete_button_icon.test.tsx new file mode 100644 index 0000000000000..2aeb46303c4b3 --- /dev/null +++ b/x-pack/plugins/cases/public/components/files/file_delete_button_icon.test.tsx @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import type { AppMockRenderer } from '../../common/mock'; + +import { createAppMockRenderer } from '../../common/mock'; +import { basicCaseId, basicFileMock } from '../../containers/mock'; +import { useDeleteFileAttachment } from '../../containers/use_delete_file_attachment'; +import { FileDeleteButtonIcon } from './file_delete_button_icon'; + +jest.mock('../../containers/use_delete_file_attachment'); + +const useDeleteFileAttachmentMock = useDeleteFileAttachment as jest.Mock; + +describe('FileDeleteButtonIcon', () => { + let appMockRender: AppMockRenderer; + const mutate = jest.fn(); + + useDeleteFileAttachmentMock.mockReturnValue({ isLoading: false, mutate }); + + beforeEach(() => { + jest.clearAllMocks(); + appMockRender = createAppMockRenderer(); + }); + + it('renders delete button correctly', async () => { + appMockRender.render(); + + expect(await screen.findByTestId('cases-files-delete-button')).toBeInTheDocument(); + + expect(useDeleteFileAttachmentMock).toBeCalledTimes(1); + }); + + it('clicking delete button calls deleteFileAttachment with proper params', async () => { + appMockRender.render(); + + const deleteButton = await screen.findByTestId('cases-files-delete-button'); + + expect(deleteButton).toBeInTheDocument(); + + userEvent.click(deleteButton); + + await waitFor(() => { + expect(mutate).toHaveBeenCalledTimes(1); + expect(mutate).toHaveBeenCalledWith({ + caseId: basicCaseId, + fileId: basicFileMock.id, + successToasterTitle: 'File deleted successfully', + }); + }); + }); +}); diff --git a/x-pack/plugins/cases/public/containers/__mocks__/api.ts b/x-pack/plugins/cases/public/containers/__mocks__/api.ts index 000c8a2f4634b..b29e45f6f101e 100644 --- a/x-pack/plugins/cases/public/containers/__mocks__/api.ts +++ b/x-pack/plugins/cases/public/containers/__mocks__/api.ts @@ -162,3 +162,13 @@ export const getCaseConnectors = async ( export const getCaseUsers = async (caseId: string, signal: AbortSignal): Promise => Promise.resolve(getCaseUsersMockResponse()); + +export const deleteFileAttachments = async ({ + caseId, + fileIds, + signal, +}: { + caseId: string; + fileIds: string[]; + signal: AbortSignal; +}): Promise => Promise.resolve(undefined); diff --git a/x-pack/plugins/cases/public/containers/api.test.tsx b/x-pack/plugins/cases/public/containers/api.test.tsx index e4d7626a36402..3f623795c819c 100644 --- a/x-pack/plugins/cases/public/containers/api.test.tsx +++ b/x-pack/plugins/cases/public/containers/api.test.tsx @@ -16,6 +16,7 @@ import { INTERNAL_BULK_CREATE_ATTACHMENTS_URL, SECURITY_SOLUTION_OWNER, INTERNAL_GET_CASE_USER_ACTIONS_STATS_URL, + INTERNAL_DELETE_FILE_ATTACHMENTS_URL, } from '../../common/constants'; import { @@ -37,6 +38,7 @@ import { postComment, getCaseConnectors, getCaseUserActionsStats, + deleteFileAttachments, } from './api'; import { @@ -59,6 +61,7 @@ import { caseUserActionsWithRegisteredAttachmentsSnake, basicPushSnake, getCaseUserActionsStatsResponse, + basicFileMock, } from './mock'; import { DEFAULT_FILTER_OPTIONS, DEFAULT_QUERY_PARAMS } from './use_get_cases'; @@ -820,6 +823,30 @@ describe('Cases API', () => { }); }); + describe('deleteFileAttachments', () => { + beforeEach(() => { + fetchMock.mockClear(); + fetchMock.mockResolvedValue(null); + }); + + it('should be called with correct url, method, signal and body', async () => { + const resp = await deleteFileAttachments({ + caseId: basicCaseId, + fileIds: [basicFileMock.id], + signal: abortCtrl.signal, + }); + expect(fetchMock).toHaveBeenCalledWith( + INTERNAL_DELETE_FILE_ATTACHMENTS_URL.replace('{case_id}', basicCaseId), + { + method: 'POST', + body: JSON.stringify({ ids: [basicFileMock.id] }), + signal: abortCtrl.signal, + } + ); + expect(resp).toBe(undefined); + }); + }); + describe('pushCase', () => { const connectorId = 'connectorId'; diff --git a/x-pack/plugins/cases/public/containers/use_delete_file_attachment.test.tsx b/x-pack/plugins/cases/public/containers/use_delete_file_attachment.test.tsx new file mode 100644 index 0000000000000..e5153185fe284 --- /dev/null +++ b/x-pack/plugins/cases/public/containers/use_delete_file_attachment.test.tsx @@ -0,0 +1,134 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { act, renderHook } from '@testing-library/react-hooks'; +import * as api from './api'; +import { basicCaseId, basicFileMock } from './mock'; +import { useRefreshCaseViewPage } from '../components/case_view/use_on_refresh_case_view_page'; +import { useToasts } from '../common/lib/kibana'; +import type { AppMockRenderer } from '../common/mock'; +import { createAppMockRenderer } from '../common/mock'; +import { useDeleteFileAttachment } from './use_delete_file_attachment'; + +jest.mock('./api'); +jest.mock('../common/lib/kibana'); +jest.mock('../components/case_view/use_on_refresh_case_view_page'); + +const successToasterTitle = 'Deleted'; + +describe('useDeleteFileAttachment', () => { + const addSuccess = jest.fn(); + const addError = jest.fn(); + + (useToasts as jest.Mock).mockReturnValue({ addSuccess, addError }); + + let appMockRender: AppMockRenderer; + + beforeEach(() => { + appMockRender = createAppMockRenderer(); + jest.clearAllMocks(); + }); + + it('init', async () => { + const { result } = renderHook(() => useDeleteFileAttachment(), { + wrapper: appMockRender.AppWrapper, + }); + + expect(result.current).toBeTruthy(); + }); + + it('calls deleteFileAttachment with correct arguments - case', async () => { + const spyOnDeleteFileAttachments = jest.spyOn(api, 'deleteFileAttachments'); + + const { waitForNextUpdate, result } = renderHook(() => useDeleteFileAttachment(), { + wrapper: appMockRender.AppWrapper, + }); + + act(() => { + result.current.mutate({ + caseId: basicCaseId, + fileId: basicFileMock.id, + successToasterTitle, + }); + }); + + await waitForNextUpdate(); + + expect(spyOnDeleteFileAttachments).toHaveBeenCalledWith({ + caseId: basicCaseId, + fileIds: [basicFileMock.id], + signal: expect.any(AbortSignal), + }); + }); + + it('refreshes the case page view', async () => { + const { waitForNextUpdate, result } = renderHook(() => useDeleteFileAttachment(), { + wrapper: appMockRender.AppWrapper, + }); + + act(() => + result.current.mutate({ + caseId: basicCaseId, + fileId: basicFileMock.id, + successToasterTitle, + }) + ); + + await waitForNextUpdate(); + + expect(useRefreshCaseViewPage()).toBeCalled(); + }); + + it('shows a success toaster correctly', async () => { + const { waitForNextUpdate, result } = renderHook(() => useDeleteFileAttachment(), { + wrapper: appMockRender.AppWrapper, + }); + + act(() => + result.current.mutate({ + caseId: basicCaseId, + fileId: basicFileMock.id, + successToasterTitle, + }) + ); + + await waitForNextUpdate(); + + expect(addSuccess).toHaveBeenCalledWith({ + title: successToasterTitle, + className: 'eui-textBreakWord', + }); + }); + + it('sets isError when fails to delete a file attachment', async () => { + const spyOnDeleteFileAttachments = jest.spyOn(api, 'deleteFileAttachments'); + spyOnDeleteFileAttachments.mockRejectedValue(new Error('Error')); + + const { waitForNextUpdate, result } = renderHook(() => useDeleteFileAttachment(), { + wrapper: appMockRender.AppWrapper, + }); + + act(() => + result.current.mutate({ + caseId: basicCaseId, + fileId: basicFileMock.id, + successToasterTitle, + }) + ); + + await waitForNextUpdate(); + + expect(spyOnDeleteFileAttachments).toBeCalledWith({ + caseId: basicCaseId, + fileIds: [basicFileMock.id], + signal: expect.any(AbortSignal), + }); + + expect(addError).toHaveBeenCalled(); + expect(result.current.isError).toBe(true); + }); +}); diff --git a/x-pack/plugins/cases/public/containers/use_delete_file_attachment.tsx b/x-pack/plugins/cases/public/containers/use_delete_file_attachment.tsx index 04b5a77946bb8..056cdff4dd0c3 100644 --- a/x-pack/plugins/cases/public/containers/use_delete_file_attachment.tsx +++ b/x-pack/plugins/cases/public/containers/use_delete_file_attachment.tsx @@ -21,7 +21,7 @@ interface MutationArgs { export const useDeleteFileAttachment = () => { const { showErrorToast, showSuccessToast } = useCasesToast(); - const refreshCaseViewPage = useRefreshCaseViewPage(); + const refreshAttachmentsTable = useRefreshCaseViewPage(); return useMutation( ({ caseId, fileId }: MutationArgs) => { @@ -32,7 +32,7 @@ export const useDeleteFileAttachment = () => { mutationKey: casesMutationsKeys.deleteFileAttachment, onSuccess: (_, { successToasterTitle }) => { showSuccessToast(successToasterTitle); - refreshCaseViewPage(); + refreshAttachmentsTable(); }, onError: (error: ServerError) => { showErrorToast(error, { title: i18n.ERROR_TITLE });