=> {
+ return Promise.resolve(basicCase);
+};
+
+export const getCasesStatus = async (signal: AbortSignal): Promise =>
+ Promise.resolve(casesStatus);
+
+export const getTags = async (signal: AbortSignal): Promise => Promise.resolve(tags);
+
+export const getReporters = async (signal: AbortSignal): Promise =>
+ Promise.resolve(respReporters);
+
+export const getCaseUserActions = async (
+ caseId: string,
+ signal: AbortSignal
+): Promise => Promise.resolve(caseUserActions);
+
+export const getCases = async ({
+ filterOptions = {
+ search: '',
+ reporters: [],
+ status: 'open',
+ tags: [],
+ },
+ queryParams = {
+ page: 1,
+ perPage: 5,
+ sortField: SortFieldCase.createdAt,
+ sortOrder: 'desc',
+ },
+ signal,
+}: FetchCasesProps): Promise => Promise.resolve(allCases);
+
+export const postCase = async (newCase: CasePostRequest, signal: AbortSignal): Promise =>
+ Promise.resolve(basicCasePost);
+
+export const patchCase = async (
+ caseId: string,
+ updatedCase: Pick,
+ version: string,
+ signal: AbortSignal
+): Promise => Promise.resolve([basicCase]);
+
+export const patchCasesStatus = async (
+ cases: BulkUpdateStatus[],
+ signal: AbortSignal
+): Promise => Promise.resolve(allCases.cases);
+
+export const postComment = async (
+ newComment: CommentRequest,
+ caseId: string,
+ signal: AbortSignal
+): Promise => Promise.resolve(basicCase);
+
+export const patchComment = async (
+ caseId: string,
+ commentId: string,
+ commentUpdate: string,
+ version: string,
+ signal: AbortSignal
+): Promise => Promise.resolve(basicCaseCommentPatch);
+
+export const deleteCases = async (caseIds: string[], signal: AbortSignal): Promise =>
+ Promise.resolve(true);
+
+export const pushCase = async (
+ caseId: string,
+ push: CaseExternalServiceRequest,
+ signal: AbortSignal
+): Promise => Promise.resolve(pushedCase);
+
+export const pushToService = async (
+ connectorId: string,
+ casePushParams: ServiceConnectorCaseParams,
+ signal: AbortSignal
+): Promise => Promise.resolve(serviceConnector);
+
+export const getActionLicense = async (signal: AbortSignal): Promise =>
+ Promise.resolve(actionLicenses);
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/api.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/api.test.tsx
new file mode 100644
index 0000000000000..4f5655cc9f221
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/containers/case/api.test.tsx
@@ -0,0 +1,463 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { KibanaServices } from '../../lib/kibana';
+import {
+ deleteCases,
+ getActionLicense,
+ getCase,
+ getCases,
+ getCasesStatus,
+ getCaseUserActions,
+ getReporters,
+ getTags,
+ patchCase,
+ patchCasesStatus,
+ patchComment,
+ postCase,
+ postComment,
+ pushCase,
+ pushToService,
+} from './api';
+import {
+ actionLicenses,
+ allCases,
+ basicCase,
+ allCasesSnake,
+ basicCaseSnake,
+ actionTypeExecutorResult,
+ pushedCaseSnake,
+ casesStatus,
+ casesSnake,
+ cases,
+ caseUserActions,
+ pushedCase,
+ pushSnake,
+ reporters,
+ respReporters,
+ serviceConnector,
+ casePushParams,
+ tags,
+ caseUserActionsSnake,
+ casesStatusSnake,
+} from './mock';
+import { CASES_URL } from './constants';
+import { DEFAULT_FILTER_OPTIONS, DEFAULT_QUERY_PARAMS } from './use_get_cases';
+import * as i18n from './translations';
+
+const abortCtrl = new AbortController();
+const mockKibanaServices = KibanaServices.get as jest.Mock;
+jest.mock('../../lib/kibana');
+
+const fetchMock = jest.fn();
+mockKibanaServices.mockReturnValue({ http: { fetch: fetchMock } });
+
+describe('Case Configuration API', () => {
+ describe('deleteCases', () => {
+ beforeEach(() => {
+ fetchMock.mockClear();
+ fetchMock.mockResolvedValue('');
+ });
+ const data = ['1', '2'];
+
+ test('check url, method, signal', async () => {
+ await deleteCases(data, abortCtrl.signal);
+ expect(fetchMock).toHaveBeenCalledWith(`${CASES_URL}`, {
+ method: 'DELETE',
+ query: { ids: JSON.stringify(data) },
+ signal: abortCtrl.signal,
+ });
+ });
+
+ test('happy path', async () => {
+ const resp = await deleteCases(data, abortCtrl.signal);
+ expect(resp).toEqual('');
+ });
+ });
+ describe('getActionLicense', () => {
+ beforeEach(() => {
+ fetchMock.mockClear();
+ fetchMock.mockResolvedValue(actionLicenses);
+ });
+ test('check url, method, signal', async () => {
+ await getActionLicense(abortCtrl.signal);
+ expect(fetchMock).toHaveBeenCalledWith(`/api/action/types`, {
+ method: 'GET',
+ signal: abortCtrl.signal,
+ });
+ });
+
+ test('happy path', async () => {
+ const resp = await getActionLicense(abortCtrl.signal);
+ expect(resp).toEqual(actionLicenses);
+ });
+ });
+ describe('getCase', () => {
+ beforeEach(() => {
+ fetchMock.mockClear();
+ fetchMock.mockResolvedValue(basicCaseSnake);
+ });
+ const data = basicCase.id;
+
+ test('check url, method, signal', async () => {
+ await getCase(data, true, abortCtrl.signal);
+ expect(fetchMock).toHaveBeenCalledWith(`${CASES_URL}/${basicCase.id}`, {
+ method: 'GET',
+ query: { includeComments: true },
+ signal: abortCtrl.signal,
+ });
+ });
+
+ test('happy path', async () => {
+ const resp = await getCase(data, true, abortCtrl.signal);
+ expect(resp).toEqual(basicCase);
+ });
+ });
+ describe('getCases', () => {
+ beforeEach(() => {
+ fetchMock.mockClear();
+ fetchMock.mockResolvedValue(allCasesSnake);
+ });
+ test('check url, method, signal', async () => {
+ await getCases({
+ filterOptions: DEFAULT_FILTER_OPTIONS,
+ queryParams: DEFAULT_QUERY_PARAMS,
+ signal: abortCtrl.signal,
+ });
+ expect(fetchMock).toHaveBeenCalledWith(`${CASES_URL}/_find`, {
+ method: 'GET',
+ query: {
+ ...DEFAULT_QUERY_PARAMS,
+ reporters: [],
+ tags: [],
+ status: 'open',
+ },
+ signal: abortCtrl.signal,
+ });
+ });
+ test('correctly applies filters', async () => {
+ await getCases({
+ filterOptions: {
+ ...DEFAULT_FILTER_OPTIONS,
+ reporters: [...respReporters, { username: null, full_name: null, email: null }],
+ tags,
+ status: '',
+ search: 'hello',
+ },
+ queryParams: DEFAULT_QUERY_PARAMS,
+ signal: abortCtrl.signal,
+ });
+ expect(fetchMock).toHaveBeenCalledWith(`${CASES_URL}/_find`, {
+ method: 'GET',
+ query: {
+ ...DEFAULT_QUERY_PARAMS,
+ reporters,
+ tags,
+ search: 'hello',
+ },
+ signal: abortCtrl.signal,
+ });
+ });
+
+ test('happy path', async () => {
+ const resp = await getCases({
+ filterOptions: DEFAULT_FILTER_OPTIONS,
+ queryParams: DEFAULT_QUERY_PARAMS,
+ signal: abortCtrl.signal,
+ });
+ expect(resp).toEqual({ ...allCases });
+ });
+ });
+ describe('getCasesStatus', () => {
+ beforeEach(() => {
+ fetchMock.mockClear();
+ fetchMock.mockResolvedValue(casesStatusSnake);
+ });
+ test('check url, method, signal', async () => {
+ await getCasesStatus(abortCtrl.signal);
+ expect(fetchMock).toHaveBeenCalledWith(`${CASES_URL}/status`, {
+ method: 'GET',
+ signal: abortCtrl.signal,
+ });
+ });
+
+ test('happy path', async () => {
+ const resp = await getCasesStatus(abortCtrl.signal);
+ expect(resp).toEqual(casesStatus);
+ });
+ });
+ describe('getCaseUserActions', () => {
+ beforeEach(() => {
+ fetchMock.mockClear();
+ fetchMock.mockResolvedValue(caseUserActionsSnake);
+ });
+
+ test('check url, method, signal', async () => {
+ await getCaseUserActions(basicCase.id, abortCtrl.signal);
+ expect(fetchMock).toHaveBeenCalledWith(`${CASES_URL}/${basicCase.id}/user_actions`, {
+ method: 'GET',
+ signal: abortCtrl.signal,
+ });
+ });
+
+ test('happy path', async () => {
+ const resp = await getCaseUserActions(basicCase.id, abortCtrl.signal);
+ expect(resp).toEqual(caseUserActions);
+ });
+ });
+ describe('getReporters', () => {
+ beforeEach(() => {
+ fetchMock.mockClear();
+ fetchMock.mockResolvedValue(respReporters);
+ });
+
+ test('check url, method, signal', async () => {
+ await getReporters(abortCtrl.signal);
+ expect(fetchMock).toHaveBeenCalledWith(`${CASES_URL}/reporters`, {
+ method: 'GET',
+ signal: abortCtrl.signal,
+ });
+ });
+
+ test('happy path', async () => {
+ const resp = await getReporters(abortCtrl.signal);
+ expect(resp).toEqual(respReporters);
+ });
+ });
+ describe('getTags', () => {
+ beforeEach(() => {
+ fetchMock.mockClear();
+ fetchMock.mockResolvedValue(tags);
+ });
+
+ test('check url, method, signal', async () => {
+ await getTags(abortCtrl.signal);
+ expect(fetchMock).toHaveBeenCalledWith(`${CASES_URL}/tags`, {
+ method: 'GET',
+ signal: abortCtrl.signal,
+ });
+ });
+
+ test('happy path', async () => {
+ const resp = await getTags(abortCtrl.signal);
+ expect(resp).toEqual(tags);
+ });
+ });
+ describe('patchCase', () => {
+ beforeEach(() => {
+ fetchMock.mockClear();
+ fetchMock.mockResolvedValue([basicCaseSnake]);
+ });
+ const data = { description: 'updated description' };
+ test('check url, method, signal', async () => {
+ await patchCase(basicCase.id, data, basicCase.version, abortCtrl.signal);
+ expect(fetchMock).toHaveBeenCalledWith(`${CASES_URL}`, {
+ method: 'PATCH',
+ body: JSON.stringify({
+ cases: [{ ...data, id: basicCase.id, version: basicCase.version }],
+ }),
+ signal: abortCtrl.signal,
+ });
+ });
+
+ test('happy path', async () => {
+ const resp = await patchCase(
+ basicCase.id,
+ { description: 'updated description' },
+ basicCase.version,
+ abortCtrl.signal
+ );
+ expect(resp).toEqual({ ...[basicCase] });
+ });
+ });
+ describe('patchCasesStatus', () => {
+ beforeEach(() => {
+ fetchMock.mockClear();
+ fetchMock.mockResolvedValue(casesSnake);
+ });
+ const data = [
+ {
+ status: 'closed',
+ id: basicCase.id,
+ version: basicCase.version,
+ },
+ ];
+
+ test('check url, method, signal', async () => {
+ await patchCasesStatus(data, abortCtrl.signal);
+ expect(fetchMock).toHaveBeenCalledWith(`${CASES_URL}`, {
+ method: 'PATCH',
+ body: JSON.stringify({ cases: data }),
+ signal: abortCtrl.signal,
+ });
+ });
+
+ test('happy path', async () => {
+ const resp = await patchCasesStatus(data, abortCtrl.signal);
+ expect(resp).toEqual({ ...cases });
+ });
+ });
+ describe('patchComment', () => {
+ beforeEach(() => {
+ fetchMock.mockClear();
+ fetchMock.mockResolvedValue(basicCaseSnake);
+ });
+
+ test('check url, method, signal', async () => {
+ await patchComment(
+ basicCase.id,
+ basicCase.comments[0].id,
+ 'updated comment',
+ basicCase.comments[0].version,
+ abortCtrl.signal
+ );
+ expect(fetchMock).toHaveBeenCalledWith(`${CASES_URL}/${basicCase.id}/comments`, {
+ method: 'PATCH',
+ body: JSON.stringify({
+ comment: 'updated comment',
+ id: basicCase.comments[0].id,
+ version: basicCase.comments[0].version,
+ }),
+ signal: abortCtrl.signal,
+ });
+ });
+
+ test('happy path', async () => {
+ const resp = await patchComment(
+ basicCase.id,
+ basicCase.comments[0].id,
+ 'updated comment',
+ basicCase.comments[0].version,
+ abortCtrl.signal
+ );
+ expect(resp).toEqual(basicCase);
+ });
+ });
+ describe('postCase', () => {
+ beforeEach(() => {
+ fetchMock.mockClear();
+ fetchMock.mockResolvedValue(basicCaseSnake);
+ });
+ const data = {
+ description: 'description',
+ tags: ['tag'],
+ title: 'title',
+ };
+
+ test('check url, method, signal', async () => {
+ await postCase(data, abortCtrl.signal);
+ expect(fetchMock).toHaveBeenCalledWith(`${CASES_URL}`, {
+ method: 'POST',
+ body: JSON.stringify(data),
+ signal: abortCtrl.signal,
+ });
+ });
+
+ test('happy path', async () => {
+ const resp = await postCase(data, abortCtrl.signal);
+ expect(resp).toEqual(basicCase);
+ });
+ });
+ describe('postComment', () => {
+ beforeEach(() => {
+ fetchMock.mockClear();
+ fetchMock.mockResolvedValue(basicCaseSnake);
+ });
+ const data = {
+ comment: 'comment',
+ };
+
+ test('check url, method, signal', async () => {
+ await postComment(data, basicCase.id, abortCtrl.signal);
+ expect(fetchMock).toHaveBeenCalledWith(`${CASES_URL}/${basicCase.id}/comments`, {
+ method: 'POST',
+ body: JSON.stringify(data),
+ signal: abortCtrl.signal,
+ });
+ });
+
+ test('happy path', async () => {
+ const resp = await postComment(data, basicCase.id, abortCtrl.signal);
+ expect(resp).toEqual(basicCase);
+ });
+ });
+ describe('pushCase', () => {
+ beforeEach(() => {
+ fetchMock.mockClear();
+ fetchMock.mockResolvedValue(pushedCaseSnake);
+ });
+
+ test('check url, method, signal', async () => {
+ await pushCase(basicCase.id, pushSnake, abortCtrl.signal);
+ expect(fetchMock).toHaveBeenCalledWith(`${CASES_URL}/${basicCase.id}/_push`, {
+ method: 'POST',
+ body: JSON.stringify(pushSnake),
+ signal: abortCtrl.signal,
+ });
+ });
+
+ test('happy path', async () => {
+ const resp = await pushCase(basicCase.id, pushSnake, abortCtrl.signal);
+ expect(resp).toEqual(pushedCase);
+ });
+ });
+ describe('pushToService', () => {
+ beforeEach(() => {
+ fetchMock.mockClear();
+ fetchMock.mockResolvedValue(actionTypeExecutorResult);
+ });
+ const connectorId = 'connectorId';
+ test('check url, method, signal', async () => {
+ await pushToService(connectorId, casePushParams, abortCtrl.signal);
+ expect(fetchMock).toHaveBeenCalledWith(`/api/action/${connectorId}/_execute`, {
+ method: 'POST',
+ body: JSON.stringify({ params: casePushParams }),
+ signal: abortCtrl.signal,
+ });
+ });
+
+ test('happy path', async () => {
+ const resp = await pushToService(connectorId, casePushParams, abortCtrl.signal);
+ expect(resp).toEqual(serviceConnector);
+ });
+
+ test('unhappy path - serviceMessage', async () => {
+ const theError = 'the error';
+ fetchMock.mockResolvedValue({
+ ...actionTypeExecutorResult,
+ status: 'error',
+ serviceMessage: theError,
+ message: 'not it',
+ });
+ await expect(
+ pushToService(connectorId, casePushParams, abortCtrl.signal)
+ ).rejects.toMatchObject({ message: theError });
+ });
+
+ test('unhappy path - message', async () => {
+ const theError = 'the error';
+ fetchMock.mockResolvedValue({
+ ...actionTypeExecutorResult,
+ status: 'error',
+ message: theError,
+ });
+ await expect(
+ pushToService(connectorId, casePushParams, abortCtrl.signal)
+ ).rejects.toMatchObject({ message: theError });
+ });
+
+ test('unhappy path - no message', async () => {
+ const theError = i18n.ERROR_PUSH_TO_SERVICE;
+ fetchMock.mockResolvedValue({
+ ...actionTypeExecutorResult,
+ status: 'error',
+ });
+ await expect(
+ pushToService(connectorId, casePushParams, abortCtrl.signal)
+ ).rejects.toMatchObject({ message: theError });
+ });
+ });
+});
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/mock.ts b/x-pack/legacy/plugins/siem/public/containers/case/mock.ts
new file mode 100644
index 0000000000000..0bda75e5bc9e0
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/containers/case/mock.ts
@@ -0,0 +1,307 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { ActionLicense, AllCases, Case, CasesStatus, CaseUserActions, Comment } from './types';
+
+import {
+ CommentResponse,
+ ServiceConnectorCaseResponse,
+ Status,
+ UserAction,
+ UserActionField,
+ CaseResponse,
+ CasesStatusResponse,
+ CaseUserActionsResponse,
+ CasesResponse,
+ CasesFindResponse,
+} from '../../../../../../plugins/case/common/api/cases';
+import { UseGetCasesState, DEFAULT_FILTER_OPTIONS, DEFAULT_QUERY_PARAMS } from './use_get_cases';
+
+export const basicCaseId = 'basic-case-id';
+const basicCommentId = 'basic-comment-id';
+const basicCreatedAt = '2020-02-19T23:06:33.798Z';
+const basicUpdatedAt = '2020-02-20T15:02:57.995Z';
+const laterTime = '2020-02-28T15:02:57.995Z';
+export const elasticUser = {
+ fullName: 'Leslie Knope',
+ username: 'lknope',
+ email: 'leslie.knope@elastic.co',
+};
+
+export const tags: string[] = ['coke', 'pepsi'];
+
+export const basicComment: Comment = {
+ comment: 'Solve this fast!',
+ id: basicCommentId,
+ createdAt: basicCreatedAt,
+ createdBy: elasticUser,
+ pushedAt: null,
+ pushedBy: null,
+ updatedAt: null,
+ updatedBy: null,
+ version: 'WzQ3LDFc',
+};
+
+export const basicCase: Case = {
+ closedAt: null,
+ closedBy: null,
+ id: basicCaseId,
+ comments: [basicComment],
+ createdAt: basicCreatedAt,
+ createdBy: elasticUser,
+ description: 'Security banana Issue',
+ externalService: null,
+ status: 'open',
+ tags,
+ title: 'Another horrible breach!!',
+ totalComment: 1,
+ updatedAt: basicUpdatedAt,
+ updatedBy: elasticUser,
+ version: 'WzQ3LDFd',
+};
+
+export const basicCasePost: Case = {
+ ...basicCase,
+ updatedAt: null,
+ updatedBy: null,
+};
+
+export const basicCommentPatch: Comment = {
+ ...basicComment,
+ updatedAt: basicUpdatedAt,
+ updatedBy: {
+ username: 'elastic',
+ },
+};
+
+export const basicCaseCommentPatch = {
+ ...basicCase,
+ comments: [basicCommentPatch],
+};
+
+export const casesStatus: CasesStatus = {
+ countClosedCases: 130,
+ countOpenCases: 20,
+};
+
+const basicPush = {
+ connectorId: 'connector_id',
+ connectorName: 'connector name',
+ externalId: 'external_id',
+ externalTitle: 'external title',
+ externalUrl: 'basicPush.com',
+ pushedAt: basicUpdatedAt,
+ pushedBy: elasticUser,
+};
+
+export const pushedCase: Case = {
+ ...basicCase,
+ externalService: basicPush,
+};
+
+export const serviceConnector: ServiceConnectorCaseResponse = {
+ number: '123',
+ incidentId: '444',
+ pushedDate: basicUpdatedAt,
+ url: 'connector.com',
+ comments: [
+ {
+ commentId: basicCommentId,
+ pushedDate: basicUpdatedAt,
+ },
+ ],
+};
+
+const basicAction = {
+ actionAt: basicCreatedAt,
+ actionBy: elasticUser,
+ oldValue: null,
+ newValue: 'what a cool value',
+ caseId: basicCaseId,
+ commentId: null,
+};
+
+export const casePushParams = {
+ actionBy: elasticUser,
+ caseId: basicCaseId,
+ createdAt: basicCreatedAt,
+ createdBy: elasticUser,
+ incidentId: null,
+ title: 'what a cool value',
+ commentId: null,
+ updatedAt: basicCreatedAt,
+ updatedBy: elasticUser,
+ description: 'nice',
+};
+export const actionTypeExecutorResult = {
+ actionId: 'string',
+ status: 'ok',
+ data: serviceConnector,
+};
+
+export const cases: Case[] = [
+ basicCase,
+ { ...pushedCase, id: '1', totalComment: 0, comments: [] },
+ { ...pushedCase, updatedAt: laterTime, id: '2', totalComment: 0, comments: [] },
+ { ...basicCase, id: '3', totalComment: 0, comments: [] },
+ { ...basicCase, id: '4', totalComment: 0, comments: [] },
+];
+
+export const allCases: AllCases = {
+ cases,
+ page: 1,
+ perPage: 5,
+ total: 10,
+ ...casesStatus,
+};
+export const actionLicenses: ActionLicense[] = [
+ {
+ id: '.servicenow',
+ name: 'ServiceNow',
+ enabled: true,
+ enabledInConfig: true,
+ enabledInLicense: true,
+ },
+];
+
+// Snake case for mock api responses
+export const elasticUserSnake = {
+ full_name: 'Leslie Knope',
+ username: 'lknope',
+ email: 'leslie.knope@elastic.co',
+};
+export const basicCommentSnake: CommentResponse = {
+ ...basicComment,
+ comment: 'Solve this fast!',
+ id: basicCommentId,
+ created_at: basicCreatedAt,
+ created_by: elasticUserSnake,
+ pushed_at: null,
+ pushed_by: null,
+ updated_at: null,
+ updated_by: null,
+};
+
+export const basicCaseSnake: CaseResponse = {
+ ...basicCase,
+ status: 'open' as Status,
+ closed_at: null,
+ closed_by: null,
+ comments: [basicCommentSnake],
+ created_at: basicCreatedAt,
+ created_by: elasticUserSnake,
+ external_service: null,
+ updated_at: basicUpdatedAt,
+ updated_by: elasticUserSnake,
+};
+
+export const casesStatusSnake: CasesStatusResponse = {
+ count_closed_cases: 130,
+ count_open_cases: 20,
+};
+
+export const pushSnake = {
+ connector_id: 'connector_id',
+ connector_name: 'connector name',
+ external_id: 'external_id',
+ external_title: 'external title',
+ external_url: 'basicPush.com',
+};
+const basicPushSnake = {
+ ...pushSnake,
+ pushed_at: basicUpdatedAt,
+ pushed_by: elasticUserSnake,
+};
+export const pushedCaseSnake = {
+ ...basicCaseSnake,
+ external_service: basicPushSnake,
+};
+
+export const reporters: string[] = ['alexis', 'kim', 'maria', 'steph'];
+export const respReporters = [
+ { username: 'alexis', full_name: null, email: null },
+ { username: 'kim', full_name: null, email: null },
+ { username: 'maria', full_name: null, email: null },
+ { username: 'steph', full_name: null, email: null },
+];
+export const casesSnake: CasesResponse = [
+ basicCaseSnake,
+ { ...pushedCaseSnake, id: '1', totalComment: 0, comments: [] },
+ { ...pushedCaseSnake, updated_at: laterTime, id: '2', totalComment: 0, comments: [] },
+ { ...basicCaseSnake, id: '3', totalComment: 0, comments: [] },
+ { ...basicCaseSnake, id: '4', totalComment: 0, comments: [] },
+];
+
+export const allCasesSnake: CasesFindResponse = {
+ cases: casesSnake,
+ page: 1,
+ per_page: 5,
+ total: 10,
+ ...casesStatusSnake,
+};
+
+const basicActionSnake = {
+ action_at: basicCreatedAt,
+ action_by: elasticUserSnake,
+ old_value: null,
+ new_value: 'what a cool value',
+ case_id: basicCaseId,
+ comment_id: null,
+};
+export const getUserActionSnake = (af: UserActionField, a: UserAction) => ({
+ ...basicActionSnake,
+ action_id: `${af[0]}-${a}`,
+ action_field: af,
+ action: a,
+ comment_id: af[0] === 'comment' ? basicCommentId : null,
+ new_value:
+ a === 'push-to-service' && af[0] === 'pushed'
+ ? JSON.stringify(basicPushSnake)
+ : basicAction.newValue,
+});
+
+export const caseUserActionsSnake: CaseUserActionsResponse = [
+ getUserActionSnake(['description'], 'create'),
+ getUserActionSnake(['comment'], 'create'),
+ getUserActionSnake(['description'], 'update'),
+];
+
+// user actions
+
+export const getUserAction = (af: UserActionField, a: UserAction) => ({
+ ...basicAction,
+ actionId: `${af[0]}-${a}`,
+ actionField: af,
+ action: a,
+ commentId: af[0] === 'comment' ? basicCommentId : null,
+ newValue:
+ a === 'push-to-service' && af[0] === 'pushed'
+ ? JSON.stringify(basicPushSnake)
+ : basicAction.newValue,
+});
+
+export const caseUserActions: CaseUserActions[] = [
+ getUserAction(['description'], 'create'),
+ getUserAction(['comment'], 'create'),
+ getUserAction(['description'], 'update'),
+];
+
+// components tests
+export const useGetCasesMockState: UseGetCasesState = {
+ data: allCases,
+ loading: [],
+ selectedCases: [],
+ isError: false,
+ queryParams: DEFAULT_QUERY_PARAMS,
+ filterOptions: DEFAULT_FILTER_OPTIONS,
+};
+
+export const basicCaseClosed: Case = {
+ ...basicCase,
+ closedAt: '2020-02-25T23:06:33.798Z',
+ closedBy: elasticUser,
+ status: 'closed',
+};
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/types.ts b/x-pack/legacy/plugins/siem/public/containers/case/types.ts
index d2a58e9eeeff4..e552f22b55fa4 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/types.ts
+++ b/x-pack/legacy/plugins/siem/public/containers/case/types.ts
@@ -31,7 +31,7 @@ export interface CaseUserActions {
export interface CaseExternalService {
pushedAt: string;
- pushedBy: string;
+ pushedBy: ElasticUser;
connectorId: string;
connectorName: string;
externalId: string;
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_bulk_update_case.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_bulk_update_case.test.tsx
new file mode 100644
index 0000000000000..329fda10424a8
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_bulk_update_case.test.tsx
@@ -0,0 +1,128 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { renderHook, act } from '@testing-library/react-hooks';
+import { useUpdateCases, UseUpdateCases } from './use_bulk_update_case';
+import { basicCase } from './mock';
+import * as api from './api';
+
+jest.mock('./api');
+
+describe('useUpdateCases', () => {
+ const abortCtrl = new AbortController();
+ beforeEach(() => {
+ jest.clearAllMocks();
+ jest.restoreAllMocks();
+ });
+
+ it('init', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useUpdateCases()
+ );
+ await waitForNextUpdate();
+ expect(result.current).toEqual({
+ isLoading: false,
+ isError: false,
+ isUpdated: false,
+ updateBulkStatus: result.current.updateBulkStatus,
+ dispatchResetIsUpdated: result.current.dispatchResetIsUpdated,
+ });
+ });
+ });
+
+ it('calls patchCase with correct arguments', async () => {
+ const spyOnPatchCases = jest.spyOn(api, 'patchCasesStatus');
+
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useUpdateCases()
+ );
+ await waitForNextUpdate();
+
+ result.current.updateBulkStatus([basicCase], 'closed');
+ await waitForNextUpdate();
+ expect(spyOnPatchCases).toBeCalledWith(
+ [
+ {
+ status: 'closed',
+ id: basicCase.id,
+ version: basicCase.version,
+ },
+ ],
+ abortCtrl.signal
+ );
+ });
+ });
+
+ it('patch cases', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useUpdateCases()
+ );
+ await waitForNextUpdate();
+ result.current.updateBulkStatus([basicCase], 'closed');
+ await waitForNextUpdate();
+ expect(result.current).toEqual({
+ isUpdated: true,
+ isLoading: false,
+ isError: false,
+ updateBulkStatus: result.current.updateBulkStatus,
+ dispatchResetIsUpdated: result.current.dispatchResetIsUpdated,
+ });
+ });
+ });
+
+ it('set isLoading to true when posting case', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useUpdateCases()
+ );
+ await waitForNextUpdate();
+ result.current.updateBulkStatus([basicCase], 'closed');
+
+ expect(result.current.isLoading).toBe(true);
+ });
+ });
+
+ it('dispatchResetIsUpdated resets is updated', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useUpdateCases()
+ );
+
+ await waitForNextUpdate();
+ result.current.updateBulkStatus([basicCase], 'closed');
+ await waitForNextUpdate();
+ expect(result.current.isUpdated).toBeTruthy();
+ result.current.dispatchResetIsUpdated();
+ expect(result.current.isUpdated).toBeFalsy();
+ });
+ });
+
+ it('unhappy path', async () => {
+ const spyOnPatchCases = jest.spyOn(api, 'patchCasesStatus');
+ spyOnPatchCases.mockImplementation(() => {
+ throw new Error('Something went wrong');
+ });
+
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useUpdateCases()
+ );
+ await waitForNextUpdate();
+ result.current.updateBulkStatus([basicCase], 'closed');
+
+ expect(result.current).toEqual({
+ isUpdated: false,
+ isLoading: false,
+ isError: true,
+ updateBulkStatus: result.current.updateBulkStatus,
+ dispatchResetIsUpdated: result.current.dispatchResetIsUpdated,
+ });
+ });
+ });
+});
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_bulk_update_case.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_bulk_update_case.tsx
index 7d040c49f1971..d0cc4d99f8f9f 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/use_bulk_update_case.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_bulk_update_case.tsx
@@ -51,12 +51,12 @@ const dataFetchReducer = (state: UpdateState, action: Action): UpdateState => {
return state;
}
};
-interface UseUpdateCase extends UpdateState {
+export interface UseUpdateCases extends UpdateState {
updateBulkStatus: (cases: Case[], status: string) => void;
dispatchResetIsUpdated: () => void;
}
-export const useUpdateCases = (): UseUpdateCase => {
+export const useUpdateCases = (): UseUpdateCases => {
const [state, dispatch] = useReducer(dataFetchReducer, {
isLoading: false,
isError: false,
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.test.tsx
new file mode 100644
index 0000000000000..45ba392f3b5b4
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.test.tsx
@@ -0,0 +1,124 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { renderHook, act } from '@testing-library/react-hooks';
+import { useDeleteCases, UseDeleteCase } from './use_delete_cases';
+import * as api from './api';
+
+jest.mock('./api');
+
+describe('useDeleteCases', () => {
+ const abortCtrl = new AbortController();
+ const deleteObj = [{ id: '1' }, { id: '2' }, { id: '3' }];
+ const deleteArr = ['1', '2', '3'];
+ it('init', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useDeleteCases()
+ );
+ await waitForNextUpdate();
+ expect(result.current).toEqual({
+ isDisplayConfirmDeleteModal: false,
+ isLoading: false,
+ isError: false,
+ isDeleted: false,
+ dispatchResetIsDeleted: result.current.dispatchResetIsDeleted,
+ handleOnDeleteConfirm: result.current.handleOnDeleteConfirm,
+ handleToggleModal: result.current.handleToggleModal,
+ });
+ });
+ });
+
+ it('calls deleteCases with correct arguments', async () => {
+ const spyOnDeleteCases = jest.spyOn(api, 'deleteCases');
+
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useDeleteCases()
+ );
+ await waitForNextUpdate();
+
+ result.current.handleOnDeleteConfirm(deleteObj);
+ await waitForNextUpdate();
+ expect(spyOnDeleteCases).toBeCalledWith(deleteArr, abortCtrl.signal);
+ });
+ });
+
+ it('deletes cases', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useDeleteCases()
+ );
+ await waitForNextUpdate();
+ result.current.handleToggleModal();
+ result.current.handleOnDeleteConfirm(deleteObj);
+ await waitForNextUpdate();
+ expect(result.current).toEqual({
+ isDisplayConfirmDeleteModal: false,
+ isLoading: false,
+ isError: false,
+ isDeleted: true,
+ dispatchResetIsDeleted: result.current.dispatchResetIsDeleted,
+ handleOnDeleteConfirm: result.current.handleOnDeleteConfirm,
+ handleToggleModal: result.current.handleToggleModal,
+ });
+ });
+ });
+
+ it('resets is deleting', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useDeleteCases()
+ );
+ await waitForNextUpdate();
+ result.current.handleToggleModal();
+ result.current.handleOnDeleteConfirm(deleteObj);
+ await waitForNextUpdate();
+ expect(result.current.isDeleted).toBeTruthy();
+ result.current.handleToggleModal();
+ result.current.dispatchResetIsDeleted();
+ expect(result.current.isDeleted).toBeFalsy();
+ });
+ });
+
+ it('set isLoading to true when deleting cases', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useDeleteCases()
+ );
+ await waitForNextUpdate();
+ result.current.handleToggleModal();
+ result.current.handleOnDeleteConfirm(deleteObj);
+ expect(result.current.isLoading).toBe(true);
+ });
+ });
+
+ it('unhappy path', async () => {
+ const spyOnDeleteCases = jest.spyOn(api, 'deleteCases');
+ spyOnDeleteCases.mockImplementation(() => {
+ throw new Error('Something went wrong');
+ });
+
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useDeleteCases()
+ );
+ await waitForNextUpdate();
+ result.current.handleToggleModal();
+ result.current.handleOnDeleteConfirm(deleteObj);
+
+ expect(result.current).toEqual({
+ isDisplayConfirmDeleteModal: false,
+ isLoading: false,
+ isError: true,
+ isDeleted: false,
+ dispatchResetIsDeleted: result.current.dispatchResetIsDeleted,
+ handleOnDeleteConfirm: result.current.handleOnDeleteConfirm,
+ handleToggleModal: result.current.handleToggleModal,
+ });
+ });
+ });
+});
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx
index 07e3786758aeb..3c49be551c064 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx
@@ -59,9 +59,9 @@ const dataFetchReducer = (state: DeleteState, action: Action): DeleteState => {
}
};
-interface UseDeleteCase extends DeleteState {
+export interface UseDeleteCase extends DeleteState {
dispatchResetIsDeleted: () => void;
- handleOnDeleteConfirm: (caseIds: DeleteCase[]) => void;
+ handleOnDeleteConfirm: (cases: DeleteCase[]) => void;
handleToggleModal: () => void;
}
@@ -117,8 +117,8 @@ export const useDeleteCases = (): UseDeleteCase => {
}, [state.isDisplayConfirmDeleteModal]);
const handleOnDeleteConfirm = useCallback(
- caseIds => {
- dispatchDeleteCases(caseIds);
+ (cases: DeleteCase[]) => {
+ dispatchDeleteCases(cases);
dispatchToggleDeleteModal();
},
[state.isDisplayConfirmDeleteModal]
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_action_license.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_action_license.test.tsx
new file mode 100644
index 0000000000000..23c9ff5e49586
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_action_license.test.tsx
@@ -0,0 +1,89 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { renderHook, act } from '@testing-library/react-hooks';
+import { initialData, useGetActionLicense, ActionLicenseState } from './use_get_action_license';
+import { actionLicenses } from './mock';
+import * as api from './api';
+
+jest.mock('./api');
+
+describe('useGetActionLicense', () => {
+ const abortCtrl = new AbortController();
+ beforeEach(() => {
+ jest.clearAllMocks();
+ jest.restoreAllMocks();
+ });
+
+ it('init', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useGetActionLicense()
+ );
+ await waitForNextUpdate();
+ expect(result.current).toEqual(initialData);
+ });
+ });
+
+ it('calls getActionLicense with correct arguments', async () => {
+ const spyOnGetActionLicense = jest.spyOn(api, 'getActionLicense');
+
+ await act(async () => {
+ const { waitForNextUpdate } = renderHook(() =>
+ useGetActionLicense()
+ );
+ await waitForNextUpdate();
+ await waitForNextUpdate();
+ expect(spyOnGetActionLicense).toBeCalledWith(abortCtrl.signal);
+ });
+ });
+
+ it('gets action license', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useGetActionLicense()
+ );
+ await waitForNextUpdate();
+ await waitForNextUpdate();
+ expect(result.current).toEqual({
+ isLoading: false,
+ isError: false,
+ actionLicense: actionLicenses[0],
+ });
+ });
+ });
+
+ it('set isLoading to true when posting case', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useGetActionLicense()
+ );
+ await waitForNextUpdate();
+ expect(result.current.isLoading).toBe(true);
+ });
+ });
+
+ it('unhappy path', async () => {
+ const spyOnGetActionLicense = jest.spyOn(api, 'getActionLicense');
+ spyOnGetActionLicense.mockImplementation(() => {
+ throw new Error('Something went wrong');
+ });
+
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useGetActionLicense()
+ );
+ await waitForNextUpdate();
+ await waitForNextUpdate();
+
+ expect(result.current).toEqual({
+ actionLicense: null,
+ isLoading: false,
+ isError: true,
+ });
+ });
+ });
+});
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_action_license.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_action_license.tsx
index 12f92b2db039b..0d28a1b20c61f 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_action_license.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_action_license.tsx
@@ -11,13 +11,13 @@ import { getActionLicense } from './api';
import * as i18n from './translations';
import { ActionLicense } from './types';
-interface ActionLicenseState {
+export interface ActionLicenseState {
actionLicense: ActionLicense | null;
isLoading: boolean;
isError: boolean;
}
-const initialData: ActionLicenseState = {
+export const initialData: ActionLicenseState = {
actionLicense: null,
isLoading: true,
isError: false,
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.test.tsx
new file mode 100644
index 0000000000000..10649da548d43
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.test.tsx
@@ -0,0 +1,112 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { renderHook, act } from '@testing-library/react-hooks';
+import { initialData, useGetCase, UseGetCase } from './use_get_case';
+import { basicCase } from './mock';
+import * as api from './api';
+
+jest.mock('./api');
+
+describe('useGetCase', () => {
+ const abortCtrl = new AbortController();
+ beforeEach(() => {
+ jest.clearAllMocks();
+ jest.restoreAllMocks();
+ });
+
+ it('init', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useGetCase(basicCase.id)
+ );
+ await waitForNextUpdate();
+ expect(result.current).toEqual({
+ data: initialData,
+ isLoading: true,
+ isError: false,
+ fetchCase: result.current.fetchCase,
+ updateCase: result.current.updateCase,
+ });
+ });
+ });
+
+ it('calls getCase with correct arguments', async () => {
+ const spyOnGetCase = jest.spyOn(api, 'getCase');
+ await act(async () => {
+ const { waitForNextUpdate } = renderHook(() => useGetCase(basicCase.id));
+ await waitForNextUpdate();
+ await waitForNextUpdate();
+ expect(spyOnGetCase).toBeCalledWith(basicCase.id, true, abortCtrl.signal);
+ });
+ });
+
+ it('fetch case', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useGetCase(basicCase.id)
+ );
+ await waitForNextUpdate();
+ await waitForNextUpdate();
+ expect(result.current).toEqual({
+ data: basicCase,
+ isLoading: false,
+ isError: false,
+ fetchCase: result.current.fetchCase,
+ updateCase: result.current.updateCase,
+ });
+ });
+ });
+
+ it('refetch case', async () => {
+ const spyOnGetCase = jest.spyOn(api, 'getCase');
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useGetCase(basicCase.id)
+ );
+ await waitForNextUpdate();
+ await waitForNextUpdate();
+ result.current.fetchCase();
+ expect(spyOnGetCase).toHaveBeenCalledTimes(2);
+ });
+ });
+
+ it('set isLoading to true when refetching case', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useGetCase(basicCase.id)
+ );
+ await waitForNextUpdate();
+ await waitForNextUpdate();
+ result.current.fetchCase();
+
+ expect(result.current.isLoading).toBe(true);
+ });
+ });
+
+ it('unhappy path', async () => {
+ const spyOnGetCase = jest.spyOn(api, 'getCase');
+ spyOnGetCase.mockImplementation(() => {
+ throw new Error('Something went wrong');
+ });
+
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useGetCase(basicCase.id)
+ );
+ await waitForNextUpdate();
+ await waitForNextUpdate();
+
+ expect(result.current).toEqual({
+ data: initialData,
+ isLoading: false,
+ isError: true,
+ fetchCase: result.current.fetchCase,
+ updateCase: result.current.updateCase,
+ });
+ });
+ });
+});
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx
index 835fb7153dc95..b2e3b6d0cacf6 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx
@@ -53,7 +53,7 @@ const dataFetchReducer = (state: CaseState, action: Action): CaseState => {
return state;
}
};
-const initialData: Case = {
+export const initialData: Case = {
id: '',
closedAt: null,
closedBy: null,
@@ -73,7 +73,7 @@ const initialData: Case = {
version: '',
};
-interface UseGetCase extends CaseState {
+export interface UseGetCase extends CaseState {
fetchCase: () => void;
updateCase: (newCase: Case) => void;
}
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_case_user_actions.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case_user_actions.test.tsx
new file mode 100644
index 0000000000000..cdd40b84f8724
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case_user_actions.test.tsx
@@ -0,0 +1,106 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { renderHook, act } from '@testing-library/react-hooks';
+import {
+ initialData,
+ useGetCaseUserActions,
+ UseGetCaseUserActions,
+} from './use_get_case_user_actions';
+import { basicCaseId, caseUserActions, elasticUser } from './mock';
+import * as api from './api';
+
+jest.mock('./api');
+
+describe('useGetCaseUserActions', () => {
+ const abortCtrl = new AbortController();
+ beforeEach(() => {
+ jest.clearAllMocks();
+ jest.restoreAllMocks();
+ });
+
+ it('init', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useGetCaseUserActions(basicCaseId)
+ );
+ await waitForNextUpdate();
+ expect(result.current).toEqual({
+ ...initialData,
+ fetchCaseUserActions: result.current.fetchCaseUserActions,
+ });
+ });
+ });
+
+ it('calls getCaseUserActions with correct arguments', async () => {
+ const spyOnPostCase = jest.spyOn(api, 'getCaseUserActions');
+
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useGetCaseUserActions(basicCaseId)
+ );
+ await waitForNextUpdate();
+
+ result.current.fetchCaseUserActions(basicCaseId);
+ await waitForNextUpdate();
+ expect(spyOnPostCase).toBeCalledWith(basicCaseId, abortCtrl.signal);
+ });
+ });
+
+ it('retuns proper state on getCaseUserActions', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useGetCaseUserActions(basicCaseId)
+ );
+ await waitForNextUpdate();
+ result.current.fetchCaseUserActions(basicCaseId);
+ await waitForNextUpdate();
+ expect(result.current).toEqual({
+ ...initialData,
+ caseUserActions: caseUserActions.slice(1),
+ fetchCaseUserActions: result.current.fetchCaseUserActions,
+ hasDataToPush: true,
+ isError: false,
+ isLoading: false,
+ participants: [elasticUser],
+ });
+ });
+ });
+
+ it('set isLoading to true when posting case', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useGetCaseUserActions(basicCaseId)
+ );
+ await waitForNextUpdate();
+ result.current.fetchCaseUserActions(basicCaseId);
+
+ expect(result.current.isLoading).toBe(true);
+ });
+ });
+
+ it('unhappy path', async () => {
+ const spyOnPostCase = jest.spyOn(api, 'getCaseUserActions');
+ spyOnPostCase.mockImplementation(() => {
+ throw new Error('Something went wrong');
+ });
+
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useGetCaseUserActions(basicCaseId)
+ );
+ await waitForNextUpdate();
+ result.current.fetchCaseUserActions(basicCaseId);
+
+ expect(result.current).toEqual({
+ ...initialData,
+ isLoading: false,
+ isError: true,
+ fetchCaseUserActions: result.current.fetchCaseUserActions,
+ });
+ });
+ });
+});
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_case_user_actions.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case_user_actions.tsx
index 4c278bc038134..6d9874a655e97 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_case_user_actions.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case_user_actions.tsx
@@ -22,7 +22,7 @@ interface CaseUserActionsState {
lastIndexPushToService: number;
}
-const initialData: CaseUserActionsState = {
+export const initialData: CaseUserActionsState = {
caseUserActions: [],
firstIndexPushToService: -1,
lastIndexPushToService: -1,
@@ -32,7 +32,7 @@ const initialData: CaseUserActionsState = {
participants: [],
};
-interface UseGetCaseUserActions extends CaseUserActionsState {
+export interface UseGetCaseUserActions extends CaseUserActionsState {
fetchCaseUserActions: (caseId: string) => void;
}
@@ -80,6 +80,7 @@ export const useGetCaseUserActions = (caseId: string): UseGetCaseUserActions =>
const participants = !isEmpty(response)
? uniqBy('actionBy.username', response).map(cau => cau.actionBy)
: [];
+
const caseUserActions = !isEmpty(response) ? response.slice(1) : [];
setCaseUserActionsState({
caseUserActions,
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.test.tsx
new file mode 100644
index 0000000000000..4e274e074b036
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.test.tsx
@@ -0,0 +1,202 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { renderHook, act } from '@testing-library/react-hooks';
+import {
+ DEFAULT_FILTER_OPTIONS,
+ DEFAULT_QUERY_PARAMS,
+ initialData,
+ useGetCases,
+ UseGetCases,
+} from './use_get_cases';
+import { UpdateKey } from './use_update_case';
+import { allCases, basicCase } from './mock';
+import * as api from './api';
+
+jest.mock('./api');
+
+describe('useGetCases', () => {
+ const abortCtrl = new AbortController();
+ beforeEach(() => {
+ jest.clearAllMocks();
+ jest.restoreAllMocks();
+ });
+
+ it('init', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() => useGetCases());
+ await waitForNextUpdate();
+ expect(result.current).toEqual({
+ data: initialData,
+ dispatchUpdateCaseProperty: result.current.dispatchUpdateCaseProperty,
+ filterOptions: DEFAULT_FILTER_OPTIONS,
+ isError: false,
+ loading: [],
+ queryParams: DEFAULT_QUERY_PARAMS,
+ refetchCases: result.current.refetchCases,
+ selectedCases: [],
+ setFilters: result.current.setFilters,
+ setQueryParams: result.current.setQueryParams,
+ setSelectedCases: result.current.setSelectedCases,
+ });
+ });
+ });
+
+ it('calls getCases with correct arguments', async () => {
+ const spyOnGetCases = jest.spyOn(api, 'getCases');
+ await act(async () => {
+ const { waitForNextUpdate } = renderHook(() => useGetCases());
+ await waitForNextUpdate();
+ await waitForNextUpdate();
+ expect(spyOnGetCases).toBeCalledWith({
+ filterOptions: DEFAULT_FILTER_OPTIONS,
+ queryParams: DEFAULT_QUERY_PARAMS,
+ signal: abortCtrl.signal,
+ });
+ });
+ });
+
+ it('fetch cases', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() => useGetCases());
+ await waitForNextUpdate();
+ await waitForNextUpdate();
+ expect(result.current).toEqual({
+ data: allCases,
+ dispatchUpdateCaseProperty: result.current.dispatchUpdateCaseProperty,
+ filterOptions: DEFAULT_FILTER_OPTIONS,
+ isError: false,
+ loading: [],
+ queryParams: DEFAULT_QUERY_PARAMS,
+ refetchCases: result.current.refetchCases,
+ selectedCases: [],
+ setFilters: result.current.setFilters,
+ setQueryParams: result.current.setQueryParams,
+ setSelectedCases: result.current.setSelectedCases,
+ });
+ });
+ });
+ it('dispatch update case property', async () => {
+ const spyOnPatchCase = jest.spyOn(api, 'patchCase');
+ await act(async () => {
+ const updateCase = {
+ updateKey: 'description' as UpdateKey,
+ updateValue: 'description update',
+ caseId: basicCase.id,
+ refetchCasesStatus: jest.fn(),
+ version: '99999',
+ };
+ const { result, waitForNextUpdate } = renderHook(() => useGetCases());
+ await waitForNextUpdate();
+ await waitForNextUpdate();
+ result.current.dispatchUpdateCaseProperty(updateCase);
+ expect(result.current.loading).toEqual(['caseUpdate']);
+ expect(spyOnPatchCase).toBeCalledWith(
+ basicCase.id,
+ { [updateCase.updateKey]: updateCase.updateValue },
+ updateCase.version,
+ abortCtrl.signal
+ );
+ });
+ });
+
+ it('refetch cases', async () => {
+ const spyOnGetCases = jest.spyOn(api, 'getCases');
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() => useGetCases());
+ await waitForNextUpdate();
+ await waitForNextUpdate();
+ result.current.refetchCases();
+ expect(spyOnGetCases).toHaveBeenCalledTimes(2);
+ });
+ });
+
+ it('set isLoading to true when refetching case', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() => useGetCases());
+ await waitForNextUpdate();
+ await waitForNextUpdate();
+ result.current.refetchCases();
+
+ expect(result.current.loading).toEqual(['cases']);
+ });
+ });
+
+ it('unhappy path', async () => {
+ const spyOnGetCases = jest.spyOn(api, 'getCases');
+ spyOnGetCases.mockImplementation(() => {
+ throw new Error('Something went wrong');
+ });
+
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() => useGetCases());
+ await waitForNextUpdate();
+ await waitForNextUpdate();
+
+ expect(result.current).toEqual({
+ data: initialData,
+ dispatchUpdateCaseProperty: result.current.dispatchUpdateCaseProperty,
+ filterOptions: DEFAULT_FILTER_OPTIONS,
+ isError: true,
+ loading: [],
+ queryParams: DEFAULT_QUERY_PARAMS,
+ refetchCases: result.current.refetchCases,
+ selectedCases: [],
+ setFilters: result.current.setFilters,
+ setQueryParams: result.current.setQueryParams,
+ setSelectedCases: result.current.setSelectedCases,
+ });
+ });
+ });
+ it('set filters', async () => {
+ await act(async () => {
+ const spyOnGetCases = jest.spyOn(api, 'getCases');
+ const newFilters = {
+ search: 'new',
+ tags: ['new'],
+ status: 'closed',
+ };
+ const { result, waitForNextUpdate } = renderHook(() => useGetCases());
+ await waitForNextUpdate();
+ await waitForNextUpdate();
+ result.current.setFilters(newFilters);
+ await waitForNextUpdate();
+ expect(spyOnGetCases.mock.calls[1][0]).toEqual({
+ filterOptions: { ...DEFAULT_FILTER_OPTIONS, ...newFilters },
+ queryParams: DEFAULT_QUERY_PARAMS,
+ signal: abortCtrl.signal,
+ });
+ });
+ });
+ it('set query params', async () => {
+ await act(async () => {
+ const spyOnGetCases = jest.spyOn(api, 'getCases');
+ const newQueryParams = {
+ page: 2,
+ };
+ const { result, waitForNextUpdate } = renderHook(() => useGetCases());
+ await waitForNextUpdate();
+ await waitForNextUpdate();
+ result.current.setQueryParams(newQueryParams);
+ await waitForNextUpdate();
+ expect(spyOnGetCases.mock.calls[1][0]).toEqual({
+ filterOptions: DEFAULT_FILTER_OPTIONS,
+ queryParams: { ...DEFAULT_QUERY_PARAMS, ...newQueryParams },
+ signal: abortCtrl.signal,
+ });
+ });
+ });
+ it('set selected cases', async () => {
+ await act(async () => {
+ const selectedCases = [basicCase];
+ const { result, waitForNextUpdate } = renderHook(() => useGetCases());
+ await waitForNextUpdate();
+ await waitForNextUpdate();
+ result.current.setSelectedCases(selectedCases);
+ expect(result.current.selectedCases).toEqual(selectedCases);
+ });
+ });
+});
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx
index 1cbce5af6304b..465b50dbdc1bc 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx
@@ -105,7 +105,7 @@ export const DEFAULT_QUERY_PARAMS: QueryParams = {
sortOrder: 'desc',
};
-const initialData: AllCases = {
+export const initialData: AllCases = {
cases: [],
countClosedCases: null,
countOpenCases: null,
@@ -113,7 +113,7 @@ const initialData: AllCases = {
perPage: 0,
total: 0,
};
-interface UseGetCases extends UseGetCasesState {
+export interface UseGetCases extends UseGetCasesState {
dispatchUpdateCaseProperty: ({
updateKey,
updateValue,
@@ -121,7 +121,7 @@ interface UseGetCases extends UseGetCasesState {
version,
refetchCasesStatus,
}: UpdateCase) => void;
- refetchCases: (filters: FilterOptions, queryParams: QueryParams) => void;
+ refetchCases: () => void;
setFilters: (filters: Partial) => void;
setQueryParams: (queryParams: Partial) => void;
setSelectedCases: (mySelectedCases: Case[]) => void;
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases_status.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases_status.test.tsx
new file mode 100644
index 0000000000000..bfbcbd2525e3b
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases_status.test.tsx
@@ -0,0 +1,88 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { renderHook, act } from '@testing-library/react-hooks';
+import { useGetCasesStatus, UseGetCasesStatus } from './use_get_cases_status';
+import { casesStatus } from './mock';
+import * as api from './api';
+
+jest.mock('./api');
+
+describe('useGetCasesStatus', () => {
+ const abortCtrl = new AbortController();
+ beforeEach(() => {
+ jest.clearAllMocks();
+ jest.restoreAllMocks();
+ });
+
+ it('init', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useGetCasesStatus()
+ );
+ await waitForNextUpdate();
+ expect(result.current).toEqual({
+ countClosedCases: null,
+ countOpenCases: null,
+ isLoading: true,
+ isError: false,
+ fetchCasesStatus: result.current.fetchCasesStatus,
+ });
+ });
+ });
+
+ it('calls getCasesStatus api', async () => {
+ const spyOnGetCasesStatus = jest.spyOn(api, 'getCasesStatus');
+ await act(async () => {
+ const { waitForNextUpdate } = renderHook(() =>
+ useGetCasesStatus()
+ );
+ await waitForNextUpdate();
+ await waitForNextUpdate();
+ expect(spyOnGetCasesStatus).toBeCalledWith(abortCtrl.signal);
+ });
+ });
+
+ it('fetch reporters', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useGetCasesStatus()
+ );
+ await waitForNextUpdate();
+ await waitForNextUpdate();
+ expect(result.current).toEqual({
+ countClosedCases: casesStatus.countClosedCases,
+ countOpenCases: casesStatus.countOpenCases,
+ isLoading: false,
+ isError: false,
+ fetchCasesStatus: result.current.fetchCasesStatus,
+ });
+ });
+ });
+
+ it('unhappy path', async () => {
+ const spyOnGetCasesStatus = jest.spyOn(api, 'getCasesStatus');
+ spyOnGetCasesStatus.mockImplementation(() => {
+ throw new Error('Something went wrong');
+ });
+
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useGetCasesStatus()
+ );
+ await waitForNextUpdate();
+ await waitForNextUpdate();
+
+ expect(result.current).toEqual({
+ countClosedCases: 0,
+ countOpenCases: 0,
+ isLoading: false,
+ isError: true,
+ fetchCasesStatus: result.current.fetchCasesStatus,
+ });
+ });
+ });
+});
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases_status.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases_status.tsx
index 7f56d27ef160e..0788464602357 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases_status.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases_status.tsx
@@ -23,7 +23,7 @@ const initialData: CasesStatusState = {
isError: false,
};
-interface UseGetCasesStatus extends CasesStatusState {
+export interface UseGetCasesStatus extends CasesStatusState {
fetchCasesStatus: () => void;
}
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_reporters.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_reporters.test.tsx
new file mode 100644
index 0000000000000..3629fbc60e4d3
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_reporters.test.tsx
@@ -0,0 +1,86 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { renderHook, act } from '@testing-library/react-hooks';
+import { useGetReporters, UseGetReporters } from './use_get_reporters';
+import { reporters, respReporters } from './mock';
+import * as api from './api';
+
+jest.mock('./api');
+
+describe('useGetReporters', () => {
+ const abortCtrl = new AbortController();
+ beforeEach(() => {
+ jest.clearAllMocks();
+ jest.restoreAllMocks();
+ });
+
+ it('init', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useGetReporters()
+ );
+ await waitForNextUpdate();
+ expect(result.current).toEqual({
+ reporters: [],
+ respReporters: [],
+ isLoading: true,
+ isError: false,
+ fetchReporters: result.current.fetchReporters,
+ });
+ });
+ });
+
+ it('calls getReporters api', async () => {
+ const spyOnGetReporters = jest.spyOn(api, 'getReporters');
+ await act(async () => {
+ const { waitForNextUpdate } = renderHook(() => useGetReporters());
+ await waitForNextUpdate();
+ await waitForNextUpdate();
+ expect(spyOnGetReporters).toBeCalledWith(abortCtrl.signal);
+ });
+ });
+
+ it('fetch reporters', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useGetReporters()
+ );
+ await waitForNextUpdate();
+ await waitForNextUpdate();
+ expect(result.current).toEqual({
+ reporters,
+ respReporters,
+ isLoading: false,
+ isError: false,
+ fetchReporters: result.current.fetchReporters,
+ });
+ });
+ });
+
+ it('unhappy path', async () => {
+ const spyOnGetReporters = jest.spyOn(api, 'getReporters');
+ spyOnGetReporters.mockImplementation(() => {
+ throw new Error('Something went wrong');
+ });
+
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useGetReporters()
+ );
+ await waitForNextUpdate();
+ await waitForNextUpdate();
+
+ expect(result.current).toEqual({
+ reporters: [],
+ respReporters: [],
+ isLoading: false,
+ isError: true,
+ fetchReporters: result.current.fetchReporters,
+ });
+ });
+ });
+});
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_reporters.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_reporters.tsx
index 2478172a3394b..2fc9b8294c8e0 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_reporters.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_reporters.tsx
@@ -26,7 +26,7 @@ const initialData: ReportersState = {
isError: false,
};
-interface UseGetReporters extends ReportersState {
+export interface UseGetReporters extends ReportersState {
fetchReporters: () => void;
}
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.test.tsx
new file mode 100644
index 0000000000000..3df83d1c8a596
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.test.tsx
@@ -0,0 +1,74 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { renderHook, act } from '@testing-library/react-hooks';
+import { useGetTags, TagsState } from './use_get_tags';
+import { tags } from './mock';
+import * as api from './api';
+
+jest.mock('./api');
+
+describe('useGetTags', () => {
+ const abortCtrl = new AbortController();
+ beforeEach(() => {
+ jest.clearAllMocks();
+ jest.restoreAllMocks();
+ });
+
+ it('init', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() => useGetTags());
+ await waitForNextUpdate();
+ expect(result.current).toEqual({
+ tags: [],
+ isLoading: true,
+ isError: false,
+ });
+ });
+ });
+
+ it('calls getTags api', async () => {
+ const spyOnGetTags = jest.spyOn(api, 'getTags');
+ await act(async () => {
+ const { waitForNextUpdate } = renderHook(() => useGetTags());
+ await waitForNextUpdate();
+ await waitForNextUpdate();
+ expect(spyOnGetTags).toBeCalledWith(abortCtrl.signal);
+ });
+ });
+
+ it('fetch tags', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() => useGetTags());
+ await waitForNextUpdate();
+ await waitForNextUpdate();
+ expect(result.current).toEqual({
+ tags,
+ isLoading: false,
+ isError: false,
+ });
+ });
+ });
+
+ it('unhappy path', async () => {
+ const spyOnGetTags = jest.spyOn(api, 'getTags');
+ spyOnGetTags.mockImplementation(() => {
+ throw new Error('Something went wrong');
+ });
+
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() => useGetTags());
+ await waitForNextUpdate();
+ await waitForNextUpdate();
+
+ expect(result.current).toEqual({
+ tags: [],
+ isLoading: false,
+ isError: true,
+ });
+ });
+ });
+});
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.tsx
index b41d5aab5c07a..7c58316ac3fe9 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.tsx
@@ -10,7 +10,7 @@ import { errorToToaster, useStateToaster } from '../../components/toasters';
import { getTags } from './api';
import * as i18n from './translations';
-interface TagsState {
+export interface TagsState {
tags: string[];
isLoading: boolean;
isError: boolean;
@@ -49,7 +49,7 @@ const initialData: string[] = [];
export const useGetTags = (): TagsState => {
const [state, dispatch] = useReducer(dataFetchReducer, {
- isLoading: false,
+ isLoading: true,
isError: false,
tags: initialData,
});
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_post_case.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_post_case.test.tsx
new file mode 100644
index 0000000000000..8b105fe041d27
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_post_case.test.tsx
@@ -0,0 +1,96 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { renderHook, act } from '@testing-library/react-hooks';
+import { usePostCase, UsePostCase } from './use_post_case';
+import { basicCasePost } from './mock';
+import * as api from './api';
+
+jest.mock('./api');
+
+describe('usePostCase', () => {
+ const abortCtrl = new AbortController();
+ const samplePost = {
+ description: 'description',
+ tags: ['tags'],
+ title: 'title',
+ };
+ beforeEach(() => {
+ jest.clearAllMocks();
+ jest.restoreAllMocks();
+ });
+
+ it('init', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() => usePostCase());
+ await waitForNextUpdate();
+ expect(result.current).toEqual({
+ isLoading: false,
+ isError: false,
+ caseData: null,
+ postCase: result.current.postCase,
+ });
+ });
+ });
+
+ it('calls postCase with correct arguments', async () => {
+ const spyOnPostCase = jest.spyOn(api, 'postCase');
+
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook