mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Security Solution] Suspend process action UI (#135755)
* [Security Solution] Suspend process action UI * pr comments
This commit is contained in:
parent
de9a822c2b
commit
62bd8980a8
8 changed files with 483 additions and 10 deletions
|
@ -218,7 +218,7 @@ export type HostIsolationRequestBody = TypeOf<typeof NoParametersRequestSchema.b
|
|||
|
||||
export type ResponseActionRequestBody = TypeOf<typeof ResponseActionBodySchema>;
|
||||
|
||||
export type KillProcessRequestBody = TypeOf<typeof KillOrSuspendProcessRequestSchema.body>;
|
||||
export type KillOrSuspendProcessRequestBody = TypeOf<typeof KillOrSuspendProcessRequestSchema.body>;
|
||||
|
||||
export interface HostIsolationResponse {
|
||||
action: string;
|
||||
|
|
|
@ -6,17 +6,26 @@
|
|||
*/
|
||||
|
||||
import type {
|
||||
KillProcessRequestBody,
|
||||
KillOrSuspendProcessRequestBody,
|
||||
ResponseActionApiResponse,
|
||||
} from '../../../../common/endpoint/types';
|
||||
import { KibanaServices } from '../kibana';
|
||||
import { KILL_PROCESS_ROUTE } from '../../../../common/endpoint/constants';
|
||||
import { KILL_PROCESS_ROUTE, SUSPEND_PROCESS_ROUTE } from '../../../../common/endpoint/constants';
|
||||
|
||||
/** Kills a process specified by pid or entity id on a host running Endpoint Security */
|
||||
export const killProcess = async (
|
||||
params: KillProcessRequestBody
|
||||
export const killProcess = (
|
||||
params: KillOrSuspendProcessRequestBody
|
||||
): Promise<ResponseActionApiResponse> => {
|
||||
return KibanaServices.get().http.post<ResponseActionApiResponse>(KILL_PROCESS_ROUTE, {
|
||||
body: JSON.stringify(params),
|
||||
});
|
||||
};
|
||||
|
||||
/** Suspends a process specified by pid or entity id on a host running Endpoint Security */
|
||||
export const suspendProcess = (
|
||||
params: KillOrSuspendProcessRequestBody
|
||||
): Promise<ResponseActionApiResponse> => {
|
||||
return KibanaServices.get().http.post<ResponseActionApiResponse>(SUSPEND_PROCESS_ROUTE, {
|
||||
body: JSON.stringify(params),
|
||||
});
|
||||
};
|
||||
|
|
|
@ -10,6 +10,7 @@ import { CommandDefinition } from '../console';
|
|||
import { IsolateActionResult } from './isolate_action';
|
||||
import { ReleaseActionResult } from './release_action';
|
||||
import { KillProcessActionResult } from './kill_process_action';
|
||||
import { SuspendProcessActionResult } from './suspend_process_action';
|
||||
import { EndpointStatusActionResult } from './status_action';
|
||||
import { GetProcessesActionResult } from './get_processes_action';
|
||||
import type { ParsedArgData } from '../console/service/parsed_command_input';
|
||||
|
@ -116,6 +117,55 @@ export const getEndpointResponseActionsConsoleCommands = (
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'suspend-process',
|
||||
about: i18n.translate('xpack.securitySolution.endpointConsoleCommands.suspendProcess.about', {
|
||||
defaultMessage: 'Suspend a running process',
|
||||
}),
|
||||
RenderComponent: SuspendProcessActionResult,
|
||||
meta: {
|
||||
endpointId: endpointAgentId,
|
||||
},
|
||||
exampleUsage: 'suspend-process --pid 123',
|
||||
exampleInstruction: 'Enter a pid or an entity id to execute',
|
||||
mustHaveArgs: true,
|
||||
args: {
|
||||
comment: {
|
||||
required: false,
|
||||
allowMultiples: false,
|
||||
about: i18n.translate(
|
||||
'xpack.securitySolution.endpointConsoleCommands.suspendProcess.arg.comment',
|
||||
{ defaultMessage: 'A comment to go along with the action' }
|
||||
),
|
||||
},
|
||||
pid: {
|
||||
required: false,
|
||||
allowMultiples: false,
|
||||
exclusiveOr: true,
|
||||
about: i18n.translate(
|
||||
'xpack.securitySolution.endpointConsoleCommands.suspendProcess.pid.arg.comment',
|
||||
{
|
||||
defaultMessage:
|
||||
'A PID representing the process to suspend. You can enter a pid or an entity id, but not both.',
|
||||
}
|
||||
),
|
||||
validate: emptyArgumentValidator,
|
||||
},
|
||||
entityId: {
|
||||
required: false,
|
||||
allowMultiples: false,
|
||||
exclusiveOr: true,
|
||||
about: i18n.translate(
|
||||
'xpack.securitySolution.endpointConsoleCommands.suspendProcess.entityId.arg.comment',
|
||||
{
|
||||
defaultMessage:
|
||||
'An entity id representing the process to suspend. You can enter a pid or an entity id, but not both.',
|
||||
}
|
||||
),
|
||||
validate: emptyArgumentValidator,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
about: i18n.translate('xpack.securitySolution.endpointConsoleCommands.status.about', {
|
||||
|
|
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
* 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 type { AppContextTestRender } from '../../../common/mock/endpoint';
|
||||
import { createAppRootMockRenderer } from '../../../common/mock/endpoint';
|
||||
import {
|
||||
ConsoleManagerTestComponent,
|
||||
getConsoleManagerMockRenderResultQueriesAndActions,
|
||||
} from '../console/components/console_manager/mocks';
|
||||
import React from 'react';
|
||||
import { getEndpointResponseActionsConsoleCommands } from './endpoint_response_actions_console_commands';
|
||||
import { enterConsoleCommand } from '../console/mocks';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import { responseActionsHttpMocks } from '../../mocks/response_actions_http_mocks';
|
||||
|
||||
describe('When using the suspend-process action from response actions console', () => {
|
||||
let render: () => Promise<ReturnType<AppContextTestRender['render']>>;
|
||||
let renderResult: ReturnType<AppContextTestRender['render']>;
|
||||
let apiMocks: ReturnType<typeof responseActionsHttpMocks>;
|
||||
let consoleManagerMockAccess: ReturnType<
|
||||
typeof getConsoleManagerMockRenderResultQueriesAndActions
|
||||
>;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockedContext = createAppRootMockRenderer();
|
||||
|
||||
apiMocks = responseActionsHttpMocks(mockedContext.coreStart.http);
|
||||
|
||||
render = async () => {
|
||||
renderResult = mockedContext.render(
|
||||
<ConsoleManagerTestComponent
|
||||
registerConsoleProps={() => {
|
||||
return {
|
||||
consoleProps: {
|
||||
'data-test-subj': 'test',
|
||||
commands: getEndpointResponseActionsConsoleCommands('a.b.c'),
|
||||
},
|
||||
};
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
consoleManagerMockAccess = getConsoleManagerMockRenderResultQueriesAndActions(renderResult);
|
||||
|
||||
await consoleManagerMockAccess.clickOnRegisterNewConsole();
|
||||
await consoleManagerMockAccess.openRunningConsole();
|
||||
|
||||
return renderResult;
|
||||
};
|
||||
});
|
||||
|
||||
it('should call `suspend-process` api when command is entered', async () => {
|
||||
await render();
|
||||
enterConsoleCommand(renderResult, 'suspend-process --pid 123');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(apiMocks.responseProvider.suspendProcess).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('should accept an optional `--comment`', async () => {
|
||||
await render();
|
||||
enterConsoleCommand(renderResult, 'suspend-process --pid 123 --comment "This is a comment"');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(apiMocks.responseProvider.suspendProcess).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
body: expect.stringContaining('This is a comment'),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should only accept one `--comment`', async () => {
|
||||
await render();
|
||||
enterConsoleCommand(renderResult, 'suspend-process --pid 123 --comment "one" --comment "two"');
|
||||
|
||||
expect(renderResult.getByTestId('test-badArgument-message').textContent).toEqual(
|
||||
'Argument can only be used once: --comment'
|
||||
);
|
||||
});
|
||||
|
||||
it('should only accept one exclusive argument', async () => {
|
||||
await render();
|
||||
enterConsoleCommand(renderResult, 'suspend-process --pid 123 --entityId 123wer');
|
||||
|
||||
expect(renderResult.getByTestId('test-badArgument-message').textContent).toEqual(
|
||||
'This command supports only one of the following arguments: --pid, --entityId'
|
||||
);
|
||||
});
|
||||
|
||||
it('should check for at least one exclusive argument', async () => {
|
||||
await render();
|
||||
enterConsoleCommand(renderResult, 'suspend-process');
|
||||
|
||||
expect(renderResult.getByTestId('test-badArgument-message').textContent).toEqual(
|
||||
'This command supports only one of the following arguments: --pid, --entityId'
|
||||
);
|
||||
});
|
||||
|
||||
it('should check the pid has a given value', async () => {
|
||||
await render();
|
||||
enterConsoleCommand(renderResult, 'suspend-process --pid');
|
||||
|
||||
expect(renderResult.getByTestId('test-badArgument-message').textContent).toEqual(
|
||||
'Invalid argument value: --pid. Argument cannot be empty'
|
||||
);
|
||||
});
|
||||
|
||||
it('should check the pid has a non-empty value', async () => {
|
||||
await render();
|
||||
enterConsoleCommand(renderResult, 'suspend-process --pid " "');
|
||||
|
||||
expect(renderResult.getByTestId('test-badArgument-message').textContent).toEqual(
|
||||
'Invalid argument value: --pid. Argument cannot be empty'
|
||||
);
|
||||
});
|
||||
|
||||
it('should check the entityId has a given value', async () => {
|
||||
await render();
|
||||
enterConsoleCommand(renderResult, 'suspend-process --entityId');
|
||||
|
||||
expect(renderResult.getByTestId('test-badArgument-message').textContent).toEqual(
|
||||
'Invalid argument value: --entityId. Argument cannot be empty'
|
||||
);
|
||||
});
|
||||
|
||||
it('should check the entity id has a non-empty value', async () => {
|
||||
await render();
|
||||
enterConsoleCommand(renderResult, 'suspend-process --entityId " "');
|
||||
|
||||
expect(renderResult.getByTestId('test-badArgument-message').textContent).toEqual(
|
||||
'Invalid argument value: --entityId. Argument cannot be empty'
|
||||
);
|
||||
});
|
||||
|
||||
it('should call the action status api after creating the `suspend-process` request', async () => {
|
||||
await render();
|
||||
enterConsoleCommand(renderResult, 'suspend-process --pid 123');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(apiMocks.responseProvider.actionDetails).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show success when `suspend-process` action completes with no errors when using `pid`', async () => {
|
||||
await render();
|
||||
enterConsoleCommand(renderResult, 'suspend-process --pid 123');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(renderResult.getByTestId('suspendProcessSuccessCallout')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show success when `suspend-process` action completes with no errors when using `entityId`', async () => {
|
||||
await render();
|
||||
enterConsoleCommand(renderResult, 'suspend-process --entityId 123wer');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(renderResult.getByTestId('suspendProcessSuccessCallout')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show error if suspend-process failed to complete successfully', async () => {
|
||||
const pendingDetailResponse = apiMocks.responseProvider.actionDetails({
|
||||
path: '/api/endpoint/action/1.2.3',
|
||||
});
|
||||
pendingDetailResponse.data.wasSuccessful = false;
|
||||
pendingDetailResponse.data.errors = ['error one', 'error two'];
|
||||
apiMocks.responseProvider.actionDetails.mockReturnValue(pendingDetailResponse);
|
||||
await render();
|
||||
enterConsoleCommand(renderResult, 'suspend-process --pid 123');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(renderResult.getByTestId('suspendProcessErrorCallout').textContent).toMatch(
|
||||
/error one \| error two/
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('and when console is closed (not terminated) and then reopened', () => {
|
||||
beforeEach(() => {
|
||||
const _render = render;
|
||||
|
||||
render = async () => {
|
||||
const response = await _render();
|
||||
enterConsoleCommand(response, 'suspend-process --pid 123');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(apiMocks.responseProvider.suspendProcess).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
// Hide the console
|
||||
await consoleManagerMockAccess.hideOpenedConsole();
|
||||
|
||||
return response;
|
||||
};
|
||||
});
|
||||
|
||||
it('should NOT send the `suspend-process` request again', async () => {
|
||||
await render();
|
||||
await consoleManagerMockAccess.openRunningConsole();
|
||||
|
||||
expect(apiMocks.responseProvider.suspendProcess).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should continue to check action status when still pending', async () => {
|
||||
const pendingDetailResponse = apiMocks.responseProvider.actionDetails({
|
||||
path: '/api/endpoint/action/1.2.3',
|
||||
});
|
||||
pendingDetailResponse.data.isCompleted = false;
|
||||
apiMocks.responseProvider.actionDetails.mockReturnValue(pendingDetailResponse);
|
||||
await render();
|
||||
|
||||
expect(apiMocks.responseProvider.actionDetails).toHaveBeenCalledTimes(2);
|
||||
|
||||
await consoleManagerMockAccess.openRunningConsole();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(apiMocks.responseProvider.actionDetails).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
});
|
||||
|
||||
it('should display completion output if done (no additional API calls)', async () => {
|
||||
await render();
|
||||
|
||||
expect(apiMocks.responseProvider.actionDetails).toHaveBeenCalledTimes(1);
|
||||
|
||||
await consoleManagerMockAccess.openRunningConsole();
|
||||
|
||||
expect(apiMocks.responseProvider.actionDetails).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* 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, { memo, useEffect } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import type { ActionDetails } from '../../../../common/endpoint/types';
|
||||
import { useGetActionDetails } from '../../hooks/endpoint/use_get_action_details';
|
||||
import type { EndpointCommandDefinitionMeta } from './types';
|
||||
import { useSendSuspendProcessRequest } from '../../hooks/endpoint/use_send_suspend_process_endpoint_request';
|
||||
import type { CommandExecutionComponentProps } from '../console/types';
|
||||
import { parsedPidOrEntityIdParameter } from '../console/service/parsed_command_input';
|
||||
|
||||
export const SuspendProcessActionResult = memo<
|
||||
CommandExecutionComponentProps<
|
||||
{ comment?: string; pid?: number; entityId?: string },
|
||||
{
|
||||
actionId?: string;
|
||||
actionRequestSent?: boolean;
|
||||
completedActionDetails?: ActionDetails;
|
||||
},
|
||||
EndpointCommandDefinitionMeta
|
||||
>
|
||||
>(({ command, setStore, store, status, setStatus, ResultComponent }) => {
|
||||
const endpointId = command.commandDefinition?.meta?.endpointId;
|
||||
const { actionId, completedActionDetails } = store;
|
||||
const isPending = status === 'pending';
|
||||
const actionRequestSent = Boolean(store.actionRequestSent);
|
||||
|
||||
const { mutate, data, isSuccess, error } = useSendSuspendProcessRequest();
|
||||
|
||||
const { data: actionDetails } = useGetActionDetails(actionId ?? '-', {
|
||||
enabled: Boolean(actionId) && isPending,
|
||||
refetchInterval: isPending ? 3000 : false,
|
||||
});
|
||||
|
||||
// Send Suspend request if not yet done
|
||||
useEffect(() => {
|
||||
const parameters = parsedPidOrEntityIdParameter(command.args.args);
|
||||
|
||||
if (!actionRequestSent && endpointId && parameters) {
|
||||
mutate({
|
||||
endpoint_ids: [endpointId],
|
||||
comment: command.args.args?.comment?.[0],
|
||||
parameters,
|
||||
});
|
||||
setStore((prevState) => {
|
||||
return { ...prevState, actionRequestSent: true };
|
||||
});
|
||||
}
|
||||
}, [actionRequestSent, command.args.args, endpointId, mutate, setStore]);
|
||||
|
||||
// If suspend-process request was created, store the action id if necessary
|
||||
useEffect(() => {
|
||||
if (isSuccess && actionId !== data.data.id) {
|
||||
setStore((prevState) => {
|
||||
return { ...prevState, actionId: data.data.id };
|
||||
});
|
||||
}
|
||||
}, [actionId, data?.data.id, isSuccess, error, setStore]);
|
||||
|
||||
useEffect(() => {
|
||||
if (actionDetails?.data.isCompleted) {
|
||||
setStatus('success');
|
||||
setStore((prevState) => {
|
||||
return {
|
||||
...prevState,
|
||||
completedActionDetails: actionDetails.data,
|
||||
};
|
||||
});
|
||||
}
|
||||
}, [actionDetails?.data, setStatus, setStore]);
|
||||
|
||||
// Show nothing if still pending
|
||||
if (isPending) {
|
||||
return (
|
||||
<ResultComponent showAs="pending">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpointResponseActions.suspendProcess.pendingMessage"
|
||||
defaultMessage="Suspending process"
|
||||
/>
|
||||
</ResultComponent>
|
||||
);
|
||||
}
|
||||
|
||||
// Show errors
|
||||
if (completedActionDetails?.errors) {
|
||||
return (
|
||||
<ResultComponent
|
||||
showAs="failure"
|
||||
title={i18n.translate(
|
||||
'xpack.securitySolution.endpointResponseActions.suspendProcess.errorMessageTitle',
|
||||
{ defaultMessage: 'Suspend process action failure' }
|
||||
)}
|
||||
data-test-subj="suspendProcessErrorCallout"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpointResponseActions.suspendProcess.errorMessage"
|
||||
defaultMessage="The following errors were encountered: {errors}"
|
||||
values={{ errors: completedActionDetails.errors.join(' | ') }}
|
||||
/>
|
||||
</ResultComponent>
|
||||
);
|
||||
}
|
||||
|
||||
// Show Success
|
||||
return (
|
||||
<ResultComponent
|
||||
title={i18n.translate(
|
||||
'xpack.securitySolution.endpointResponseActions.suspendProcess.successMessageTitle',
|
||||
{ defaultMessage: 'Process suspended successfully' }
|
||||
)}
|
||||
data-test-subj="suspendProcessSuccessCallout"
|
||||
/>
|
||||
);
|
||||
});
|
||||
SuspendProcessActionResult.displayName = 'SuspendProcessActionResult';
|
|
@ -8,7 +8,7 @@
|
|||
import { useMutation, UseMutationOptions, UseMutationResult } from 'react-query';
|
||||
import { HttpFetchError } from '@kbn/core/public';
|
||||
import type {
|
||||
KillProcessRequestBody,
|
||||
KillOrSuspendProcessRequestBody,
|
||||
ResponseActionApiResponse,
|
||||
} from '../../../../common/endpoint/types';
|
||||
import { killProcess } from '../../../common/lib/process_actions';
|
||||
|
@ -21,11 +21,15 @@ export const useSendKillProcessRequest = (
|
|||
customOptions?: UseMutationOptions<
|
||||
ResponseActionApiResponse,
|
||||
HttpFetchError,
|
||||
KillProcessRequestBody
|
||||
KillOrSuspendProcessRequestBody
|
||||
>
|
||||
): UseMutationResult<ResponseActionApiResponse, HttpFetchError, KillProcessRequestBody> => {
|
||||
return useMutation<ResponseActionApiResponse, HttpFetchError, KillProcessRequestBody>(
|
||||
(processData: KillProcessRequestBody) => {
|
||||
): UseMutationResult<
|
||||
ResponseActionApiResponse,
|
||||
HttpFetchError,
|
||||
KillOrSuspendProcessRequestBody
|
||||
> => {
|
||||
return useMutation<ResponseActionApiResponse, HttpFetchError, KillOrSuspendProcessRequestBody>(
|
||||
(processData: KillOrSuspendProcessRequestBody) => {
|
||||
return killProcess(processData);
|
||||
},
|
||||
customOptions
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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 { useMutation } from 'react-query';
|
||||
import type { UseMutationOptions, UseMutationResult } from 'react-query';
|
||||
import type { HttpFetchError } from '@kbn/core/public';
|
||||
import type {
|
||||
KillOrSuspendProcessRequestBody,
|
||||
ResponseActionApiResponse,
|
||||
} from '../../../../common/endpoint/types';
|
||||
import { suspendProcess } from '../../../common/lib/process_actions';
|
||||
|
||||
/**
|
||||
* Create kill process requests
|
||||
* @param customOptions
|
||||
*/
|
||||
export const useSendSuspendProcessRequest = (
|
||||
customOptions?: UseMutationOptions<
|
||||
ResponseActionApiResponse,
|
||||
HttpFetchError,
|
||||
KillOrSuspendProcessRequestBody
|
||||
>
|
||||
): UseMutationResult<
|
||||
ResponseActionApiResponse,
|
||||
HttpFetchError,
|
||||
KillOrSuspendProcessRequestBody
|
||||
> => {
|
||||
return useMutation<ResponseActionApiResponse, HttpFetchError, KillOrSuspendProcessRequestBody>(
|
||||
(processData: KillOrSuspendProcessRequestBody) => {
|
||||
return suspendProcess(processData);
|
||||
},
|
||||
customOptions
|
||||
);
|
||||
};
|
|
@ -15,6 +15,7 @@ import {
|
|||
ISOLATE_HOST_ROUTE,
|
||||
UNISOLATE_HOST_ROUTE,
|
||||
KILL_PROCESS_ROUTE,
|
||||
SUSPEND_PROCESS_ROUTE,
|
||||
} from '../../../common/endpoint/constants';
|
||||
import {
|
||||
httpHandlerMockFactory,
|
||||
|
@ -36,6 +37,8 @@ export type ResponseActionsHttpMocksInterface = ResponseProvidersInterface<{
|
|||
|
||||
killProcess: () => ActionDetailsApiResponse;
|
||||
|
||||
suspendProcess: () => ActionDetailsApiResponse;
|
||||
|
||||
actionDetails: (options: HttpFetchOptionsWithPath) => ActionDetailsApiResponse;
|
||||
|
||||
actionList: (options: HttpFetchOptionsWithPath) => ActionListApiResponse;
|
||||
|
@ -72,6 +75,16 @@ export const responseActionsHttpMocks = httpHandlerMockFactory<ResponseActionsHt
|
|||
return { data: response };
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'suspendProcess',
|
||||
path: SUSPEND_PROCESS_ROUTE,
|
||||
method: 'post',
|
||||
handler: (): ActionDetailsApiResponse => {
|
||||
const generator = new EndpointActionGenerator('seed');
|
||||
const response = generator.generateActionDetails() as ActionDetails;
|
||||
return { data: response };
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'actionDetails',
|
||||
path: ACTION_DETAILS_ROUTE,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue