Skip to content

Commit

Permalink
fix persist exception tests
Browse files Browse the repository at this point in the history
  • Loading branch information
eokoneyo committed Nov 11, 2024
1 parent f537b89 commit 03243d9
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { Dispatch, useEffect, useState } from 'react';
import { Dispatch, useEffect, useRef, useState } from 'react';
import type {
CreateExceptionListItemSchema,
PersistHookProps,
Expand Down Expand Up @@ -47,57 +47,63 @@ export const usePersistExceptionItem = ({
const [isLoading, setIsLoading] = useState(false);
const isUpdateExceptionItem = (item: unknown): item is UpdateExceptionListItemSchema =>
Boolean(item && (item as UpdateExceptionListItemSchema).id != null);
const isSubscribed = useRef(false);

useEffect(() => {
let isSubscribed = true;
const abortCtrl = new AbortController();
let abortCtrl: AbortController | null = null;
isSubscribed.current = true;
setIsSaved(false);

const saveExceptionItem = async (): Promise<void> => {
if (exceptionListItem != null) {
try {
setIsLoading(true);

if (isUpdateExceptionItem(exceptionListItem)) {
// Please see `x-pack/plugins/lists/public/exceptions/transforms.ts` doc notes
// for context around the temporary `id`
const transformedList = transformOutput(exceptionListItem);

await updateExceptionListItem({
http,
listItem: transformedList,
signal: abortCtrl.signal,
});
} else {
// Please see `x-pack/plugins/lists/public/exceptions/transforms.ts` doc notes
// for context around the temporary `id`
const transformedList = transformNewItemOutput(exceptionListItem);

await addExceptionListItem({
http,
listItem: transformedList,
signal: abortCtrl.signal,
});
}

if (isSubscribed) {
setIsSaved(true);
}
} catch (error) {
if (isSubscribed) {
onError(error);
}
if (exceptionListItem === null) {
return;
}

try {
abortCtrl = new AbortController();
setIsLoading(true);

if (isUpdateExceptionItem(exceptionListItem)) {
// Please see `x-pack/plugins/lists/public/exceptions/transforms.ts` doc notes
// for context around the temporary `id`
const transformedList = transformOutput(exceptionListItem);

await updateExceptionListItem({
http,
listItem: transformedList,
signal: abortCtrl.signal,
});
} else {
// Please see `x-pack/plugins/lists/public/exceptions/transforms.ts` doc notes
// for context around the temporary `id`
const transformedList = transformNewItemOutput(exceptionListItem);

await addExceptionListItem({
http,
listItem: transformedList,
signal: abortCtrl.signal,
});
}
if (isSubscribed) {

if (isSubscribed.current) {
setIsSaved(true);
}
} catch (error) {
if (isSubscribed.current) {
onError(error);
}
} finally {
if (isSubscribed.current) {
setIsLoading(false);
}
}
};

saveExceptionItem();

return (): void => {
isSubscribed = false;
abortCtrl.abort();
isSubscribed.current = false;
abortCtrl?.abort();
};
}, [http, exceptionListItem, onError]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,39 @@

import { renderHook } from '@testing-library/react-hooks';
import { act, waitFor } from '@testing-library/react';
import * as api from '@kbn/securitysolution-list-api';
import { coreMock } from '@kbn/core/public/mocks';
import { PersistHookProps } from '@kbn/securitysolution-io-ts-list-types';
import {
ReturnPersistExceptionItem,
usePersistExceptionItem,
} from '@kbn/securitysolution-list-hooks';
import { coreMock } from '@kbn/core/public/mocks';
import * as api from '@kbn/securitysolution-list-api/src/api';

import { ENTRIES_WITH_IDS } from '../../../common/constants.mock';
import { getCreateExceptionListItemSchemaMock } from '../../../common/schemas/request/create_exception_list_item_schema.mock';
import { getUpdateExceptionListItemSchemaMock } from '../../../common/schemas/request/update_exception_list_item_schema.mock';
import { getExceptionListItemSchemaMock } from '../../../common/schemas/response/exception_list_item_schema.mock';

const mockKibanaHttpService = coreMock.createStart().http;
jest.mock('@kbn/securitysolution-list-api');

// TODO: Port this test over to packages/kbn-securitysolution-list-hooks/src/use_persist_exception_item/index.test.ts once the other mocks are added to the kbn package system

describe('usePersistExceptionItem', () => {
let addExceptionListItemSpy: jest.SpyInstance<ReturnType<typeof api.addEndpointExceptionList>>;
let updateExceptionListItemSpy: jest.SpyInstance<ReturnType<typeof api.updateExceptionListItem>>;
const onError = jest.fn();

beforeEach(() => {
// setup fake timers before rendering
jest.useFakeTimers();
beforeAll(() => {
addExceptionListItemSpy = jest.spyOn(api, 'addExceptionListItem');
updateExceptionListItemSpy = jest.spyOn(api, 'updateExceptionListItem');
});

(api.addEndpointExceptionList as jest.Mock).mockResolvedValue(getExceptionListItemSchemaMock());
(api.updateExceptionListItem as jest.Mock).mockResolvedValue(getExceptionListItemSchemaMock());
beforeEach(() => {
addExceptionListItemSpy.mockResolvedValue(getExceptionListItemSchemaMock());
updateExceptionListItemSpy.mockResolvedValue(getExceptionListItemSchemaMock());
});

afterEach(() => {
// restore real timers
jest.useRealTimers();
jest.clearAllMocks();
});

Expand All @@ -51,15 +52,6 @@ describe('usePersistExceptionItem', () => {
});

test('"isLoading" is "true" when exception item is being saved', async () => {
const defaultImplementation = (api.addExceptionListItem as jest.Mock).getMockImplementation();

(api.addExceptionListItem as jest.Mock).mockImplementation(function () {
// delay response to ensure that isLoading is set to true for a while
return new Promise((resolve) =>
setTimeout(() => resolve(getExceptionListItemSchemaMock()), 1000)
);
});

const { result } = renderHook<PersistHookProps, ReturnPersistExceptionItem>(() =>
usePersistExceptionItem({ http: mockKibanaHttpService, onError })
);
Expand All @@ -68,30 +60,24 @@ describe('usePersistExceptionItem', () => {
expect(result.current).toEqual([{ isLoading: false, isSaved: false }, result.current[1]])
);

await act(async () => {
act(() => {
result.current[1](getCreateExceptionListItemSchemaMock());
});

await waitFor(() => {
expect(api.addExceptionListItem).toHaveBeenCalled();
expect(result.current).toEqual([{ isLoading: true, isSaved: false }, result.current[1]]);
});

await jest.advanceTimersByTimeAsync(500);
expect(result.current).toEqual([{ isLoading: true, isSaved: false }, result.current[1]]);

await waitFor(() => {
expect(addExceptionListItemSpy).toHaveBeenCalled();
expect(result.current).toEqual([{ isLoading: false, isSaved: true }, result.current[1]]);
});

(api.addExceptionListItem as jest.Mock).mockImplementation(defaultImplementation);
});

test('"isSaved" is "true" when exception item saved successfully', async () => {
const { result } = renderHook<PersistHookProps, ReturnPersistExceptionItem>(() =>
usePersistExceptionItem({ http: mockKibanaHttpService, onError })
);

await act(async () => {
act(() => {
result.current[1](getCreateExceptionListItemSchemaMock());
});

Expand All @@ -101,15 +87,13 @@ describe('usePersistExceptionItem', () => {
});

test('it invokes "updateExceptionListItem" when payload has "id"', async () => {
const addExceptionListItem = jest.spyOn(api, 'addExceptionListItem');
const updateExceptionListItem = jest.spyOn(api, 'updateExceptionListItem');
const { result } = renderHook<PersistHookProps, ReturnPersistExceptionItem>(() =>
usePersistExceptionItem({ http: mockKibanaHttpService, onError })
);

await waitFor(() => new Promise((resolve) => resolve(null)));

await act(async () => {
act(() => {
// NOTE: Take note here passing in an exception item where it's
// entries have been enriched with ids to ensure that they get stripped
// before the call goes through
Expand All @@ -118,49 +102,53 @@ describe('usePersistExceptionItem', () => {

await waitFor(() => {
expect(result.current).toEqual([{ isLoading: false, isSaved: true }, result.current[1]]);
expect(addExceptionListItem).not.toHaveBeenCalled();
expect(updateExceptionListItem).toHaveBeenCalledWith({
http: mockKibanaHttpService,
listItem: getUpdateExceptionListItemSchemaMock(),
signal: new AbortController().signal,
});
});

expect(addExceptionListItemSpy).not.toHaveBeenCalled();
expect(updateExceptionListItemSpy).toHaveBeenCalledWith({
http: mockKibanaHttpService,
listItem: getUpdateExceptionListItemSchemaMock(),
signal: expect.any(AbortSignal),
});
});

test('it invokes "addExceptionListItem" when payload does not have "id"', async () => {
const updateExceptionListItem = jest.spyOn(api, 'updateExceptionListItem');
const addExceptionListItem = jest.spyOn(api, 'addExceptionListItem');
const { result } = renderHook<PersistHookProps, ReturnPersistExceptionItem>(() =>
usePersistExceptionItem({ http: mockKibanaHttpService, onError })
);

await waitFor(() => new Promise((resolve) => resolve(null)));
await act(async () => {

act(() => {
// NOTE: Take note here passing in an exception item where it's
// entries have been enriched with ids to ensure that they get stripped
// before the call goes through
result.current[1]({ ...getCreateExceptionListItemSchemaMock(), entries: ENTRIES_WITH_IDS });
});

await waitFor(() => {
expect(result.current).toEqual([{ isLoading: false, isSaved: true }, result.current[1]]);
expect(updateExceptionListItem).not.toHaveBeenCalled();
expect(addExceptionListItem).toHaveBeenCalledWith({
http: mockKibanaHttpService,
listItem: getCreateExceptionListItemSchemaMock(),
signal: new AbortController().signal,
});
});

expect(updateExceptionListItemSpy).not.toHaveBeenCalled();
expect(addExceptionListItemSpy).toHaveBeenCalledWith({
http: mockKibanaHttpService,
listItem: getCreateExceptionListItemSchemaMock(),
signal: expect.any(AbortSignal),
});
});

test('"onError" callback is invoked and "isSaved" is "false" when api call fails', async () => {
const error = new Error('persist rule failed');
jest.spyOn(api, 'addExceptionListItem').mockRejectedValue(error);

addExceptionListItemSpy.mockRejectedValue(error);

const { result } = renderHook<PersistHookProps, ReturnPersistExceptionItem>(() =>
usePersistExceptionItem({ http: mockKibanaHttpService, onError })
);

await waitFor(() => new Promise((resolve) => resolve(null)));
await act(async () => {
act(() => {
result.current[1](getCreateExceptionListItemSchemaMock());
});
await waitFor(() => {
Expand Down

0 comments on commit 03243d9

Please sign in to comment.