[Detections] Truncate case title in toaster when attaching an alert to case (#103228)
This commit is contained in:
parent
b8747bde68
commit
644d2ce918
|
@ -94,3 +94,9 @@ if (ENABLE_CASE_CONNECTOR) {
|
|||
|
||||
export const MAX_DOCS_PER_PAGE = 10000;
|
||||
export const MAX_CONCURRENT_SEARCHES = 10;
|
||||
|
||||
/**
|
||||
* Validation
|
||||
*/
|
||||
|
||||
export const MAX_TITLE_LENGTH = 64;
|
||||
|
|
|
@ -228,3 +228,9 @@ export const SELECTABLE_MESSAGE_COLLECTIONS = i18n.translate(
|
|||
export const SELECT_CASE_TITLE = i18n.translate('xpack.cases.common.allCases.caseModal.title', {
|
||||
defaultMessage: 'Select case',
|
||||
});
|
||||
|
||||
export const MAX_LENGTH_ERROR = (field: string, length: number) =>
|
||||
i18n.translate('xpack.cases.createCase.maxLengthError', {
|
||||
values: { field, length },
|
||||
defaultMessage: 'The length of the {field} is too long. The maximum length is {length}.',
|
||||
});
|
||||
|
|
|
@ -34,6 +34,7 @@ import { useDeleteCases } from '../../containers/use_delete_cases';
|
|||
import { ConfirmDeleteCaseModal } from '../confirm_delete_case';
|
||||
import { useKibana } from '../../common/lib/kibana';
|
||||
import { StatusContextMenu } from '../case_action_bar/status_context_menu';
|
||||
import { TruncatedText } from '../truncated_text';
|
||||
|
||||
export type CasesColumns =
|
||||
| EuiTableActionsColumnType<Case>
|
||||
|
@ -145,10 +146,10 @@ export const useCasesColumns = ({
|
|||
subCaseId={isSubCase(theCase) ? theCase.id : undefined}
|
||||
title={theCase.title}
|
||||
>
|
||||
{theCase.title}
|
||||
<TruncatedText text={theCase.title} />
|
||||
</CaseDetailsLink>
|
||||
) : (
|
||||
<span>{theCase.title}</span>
|
||||
<TruncatedText text={theCase.title} />
|
||||
);
|
||||
return theCase.status !== CaseStatuses.closed ? (
|
||||
caseDetailsLinkComponent
|
||||
|
|
|
@ -183,6 +183,36 @@ describe('Create case', () => {
|
|||
await waitFor(() => expect(postCase).toBeCalledWith(sampleData));
|
||||
});
|
||||
|
||||
it('it does not submits the title when the length is longer than 64 characters', async () => {
|
||||
const longTitle =
|
||||
'This is a title that should not be saved as it is longer than 64 characters.';
|
||||
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<FormContext onSuccess={onFormSubmitSuccess}>
|
||||
<CreateCaseForm {...defaultCreateCaseForm} />
|
||||
<SubmitCaseButton />
|
||||
</FormContext>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
act(() => {
|
||||
wrapper
|
||||
.find(`[data-test-subj="caseTitle"] input`)
|
||||
.first()
|
||||
.simulate('change', { target: { value: longTitle } });
|
||||
wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
wrapper.update();
|
||||
expect(wrapper.find('[data-test-subj="caseTitle"] .euiFormErrorText').text()).toBe(
|
||||
'The length of the title is too long. The maximum length is 64.'
|
||||
);
|
||||
});
|
||||
expect(postCase).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should toggle sync settings', async () => {
|
||||
useConnectorsMock.mockReturnValue({
|
||||
...sampleConnectorData,
|
||||
|
|
|
@ -5,12 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { CasePostRequest, ConnectorTypeFields } from '../../../common';
|
||||
import { CasePostRequest, ConnectorTypeFields, MAX_TITLE_LENGTH } from '../../../common';
|
||||
import { FIELD_TYPES, fieldValidators, FormSchema } from '../../common/shared_imports';
|
||||
import * as i18n from './translations';
|
||||
|
||||
import { OptionalFieldLabel } from './optional_field_label';
|
||||
const { emptyField } = fieldValidators;
|
||||
const { emptyField, maxLengthField } = fieldValidators;
|
||||
|
||||
export const schemaTags = {
|
||||
type: FIELD_TYPES.COMBO_BOX,
|
||||
|
@ -33,6 +33,12 @@ export const schema: FormSchema<FormProps> = {
|
|||
{
|
||||
validator: emptyField(i18n.TITLE_REQUIRED),
|
||||
},
|
||||
{
|
||||
validator: maxLengthField({
|
||||
length: MAX_TITLE_LENGTH,
|
||||
message: i18n.MAX_LENGTH_ERROR('title', MAX_TITLE_LENGTH),
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
description: {
|
||||
|
|
|
@ -7,7 +7,9 @@ exports[`Title it renders 1`] = `
|
|||
<h1
|
||||
data-test-subj="header-page-title"
|
||||
>
|
||||
Test title
|
||||
<Memo(TruncatedTextComponent)
|
||||
text="Test title"
|
||||
/>
|
||||
|
||||
<StyledEuiBetaBadge
|
||||
label="Beta"
|
||||
|
@ -17,3 +19,17 @@ exports[`Title it renders 1`] = `
|
|||
</h1>
|
||||
</EuiTitle>
|
||||
`;
|
||||
|
||||
exports[`Title it renders the title if is not a string 1`] = `
|
||||
<EuiTitle
|
||||
size="l"
|
||||
>
|
||||
<h1
|
||||
data-test-subj="header-page-title"
|
||||
>
|
||||
<span>
|
||||
Test title
|
||||
</span>
|
||||
</h1>
|
||||
</EuiTitle>
|
||||
`;
|
||||
|
|
|
@ -187,4 +187,33 @@ describe('EditableTitle', () => {
|
|||
expect(submitTitle.mock.calls[0][0]).toEqual(newTitle);
|
||||
expect(wrapper.find('[data-test-subj="editable-title-edit-icon"]').first().exists()).toBe(true);
|
||||
});
|
||||
|
||||
test('it does not submits the title when the length is longer than 64 characters', () => {
|
||||
const longTitle =
|
||||
'This is a title that should not be saved as it is longer than 64 characters.';
|
||||
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<EditableTitle {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
wrapper.find('button[data-test-subj="editable-title-edit-icon"]').simulate('click');
|
||||
wrapper.update();
|
||||
|
||||
wrapper
|
||||
.find('input[data-test-subj="editable-title-input-field"]')
|
||||
.simulate('change', { target: { value: longTitle } });
|
||||
|
||||
wrapper.find('button[data-test-subj="editable-title-submit-btn"]').simulate('click');
|
||||
wrapper.update();
|
||||
expect(wrapper.find('.euiFormErrorText').text()).toBe(
|
||||
'The length of the title is too long. The maximum length is 64.'
|
||||
);
|
||||
|
||||
expect(submitTitle).not.toHaveBeenCalled();
|
||||
expect(wrapper.find('[data-test-subj="editable-title-edit-icon"]').first().exists()).toBe(
|
||||
false
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -16,10 +16,11 @@ import {
|
|||
EuiFieldText,
|
||||
EuiButtonIcon,
|
||||
EuiLoadingSpinner,
|
||||
EuiFormRow,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { MAX_TITLE_LENGTH } from '../../../common';
|
||||
import * as i18n from './translations';
|
||||
|
||||
import { Title } from './title';
|
||||
|
||||
const MyEuiButtonIcon = styled(EuiButtonIcon)`
|
||||
|
@ -37,7 +38,7 @@ const MySpinner = styled(EuiLoadingSpinner)`
|
|||
export interface EditableTitleProps {
|
||||
userCanCrud: boolean;
|
||||
isLoading: boolean;
|
||||
title: string | React.ReactNode;
|
||||
title: string;
|
||||
onSubmit: (title: string) => void;
|
||||
}
|
||||
|
||||
|
@ -48,57 +49,72 @@ const EditableTitleComponent: React.FC<EditableTitleProps> = ({
|
|||
title,
|
||||
}) => {
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [changedTitle, onTitleChange] = useState<string>(typeof title === 'string' ? title : '');
|
||||
const [errors, setErrors] = useState<string[]>([]);
|
||||
const [newTitle, setNewTitle] = useState<string>(title);
|
||||
|
||||
const onCancel = useCallback(() => {
|
||||
setEditMode(false);
|
||||
setErrors([]);
|
||||
setNewTitle(title);
|
||||
}, [title]);
|
||||
|
||||
const onCancel = useCallback(() => setEditMode(false), []);
|
||||
const onClickEditIcon = useCallback(() => setEditMode(true), []);
|
||||
|
||||
const onClickSubmit = useCallback((): void => {
|
||||
if (changedTitle !== title) {
|
||||
onSubmit(changedTitle);
|
||||
if (newTitle.length > MAX_TITLE_LENGTH) {
|
||||
setErrors([i18n.MAX_LENGTH_ERROR('title', MAX_TITLE_LENGTH)]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (newTitle !== title) {
|
||||
onSubmit(newTitle);
|
||||
}
|
||||
setEditMode(false);
|
||||
}, [changedTitle, onSubmit, title]);
|
||||
}, [newTitle, onSubmit, title]);
|
||||
|
||||
const handleOnChange = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => onTitleChange(e.target.value),
|
||||
(e: ChangeEvent<HTMLInputElement>) => setNewTitle(e.target.value),
|
||||
[]
|
||||
);
|
||||
|
||||
const hasErrors = errors.length > 0;
|
||||
|
||||
return editMode ? (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="m" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFieldText
|
||||
onChange={handleOnChange}
|
||||
value={`${changedTitle}`}
|
||||
data-test-subj="editable-title-input-field"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexGroup gutterSize="none" responsive={false} wrap={true}>
|
||||
<EuiFormRow isInvalid={hasErrors} error={errors} fullWidth>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="m" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
color="secondary"
|
||||
data-test-subj="editable-title-submit-btn"
|
||||
fill
|
||||
iconType="save"
|
||||
onClick={onClickSubmit}
|
||||
size="s"
|
||||
>
|
||||
{i18n.SAVE}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="editable-title-cancel-btn"
|
||||
iconType="cross"
|
||||
onClick={onCancel}
|
||||
size="s"
|
||||
>
|
||||
{i18n.CANCEL}
|
||||
</EuiButtonEmpty>
|
||||
<EuiFieldText
|
||||
onChange={handleOnChange}
|
||||
value={`${newTitle}`}
|
||||
data-test-subj="editable-title-input-field"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexGroup gutterSize="none" responsive={false} wrap={true}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
color="secondary"
|
||||
data-test-subj="editable-title-submit-btn"
|
||||
fill
|
||||
iconType="save"
|
||||
onClick={onClickSubmit}
|
||||
size="s"
|
||||
>
|
||||
{i18n.SAVE}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="editable-title-cancel-btn"
|
||||
iconType="cross"
|
||||
onClick={onCancel}
|
||||
size="s"
|
||||
>
|
||||
{i18n.CANCEL}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexItem />
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexItem />
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
) : (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="none" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
|
|
|
@ -36,4 +36,10 @@ describe('Title', () => {
|
|||
|
||||
expect(wrapper.find('[data-test-subj="header-page-title"]').first().exists()).toBe(true);
|
||||
});
|
||||
|
||||
test('it renders the title if is not a string', () => {
|
||||
const wrapper = shallow(<Title title={<span>{'Test title'}</span>} />);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,10 +6,12 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { isString } from 'lodash';
|
||||
import { EuiBetaBadge, EuiBadge, EuiTitle } from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { BadgeOptions, TitleProp } from './types';
|
||||
import { TruncatedText } from '../truncated_text';
|
||||
|
||||
const StyledEuiBetaBadge = styled(EuiBetaBadge)`
|
||||
vertical-align: middle;
|
||||
|
@ -30,7 +32,7 @@ interface Props {
|
|||
const TitleComponent: React.FC<Props> = ({ title, badgeOptions }) => (
|
||||
<EuiTitle size="l">
|
||||
<h1 data-test-subj="header-page-title">
|
||||
{title}
|
||||
{isString(title) ? <TruncatedText text={title} /> : title}
|
||||
{badgeOptions && (
|
||||
<>
|
||||
{' '}
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export * from '../../common/translations';
|
||||
|
||||
export const SAVE = i18n.translate('xpack.cases.header.editableTitle.save', {
|
||||
defaultMessage: 'Save',
|
||||
});
|
||||
|
|
|
@ -19,6 +19,7 @@ import { NoCases } from './no_cases';
|
|||
import { isSubCase } from '../all_cases/helpers';
|
||||
import { MarkdownRenderer } from '../markdown_editor';
|
||||
import { FilterOptions } from '../../containers/types';
|
||||
import { TruncatedText } from '../truncated_text';
|
||||
|
||||
const MarkdownContainer = styled.div`
|
||||
max-height: 150px;
|
||||
|
@ -80,7 +81,7 @@ export const RecentCasesComp = ({
|
|||
title={c.title}
|
||||
subCaseId={isSubCase(c) ? c.id : undefined}
|
||||
>
|
||||
{c.title}
|
||||
<TruncatedText text={c.title} />
|
||||
</CaseDetailsLink>
|
||||
</EuiText>
|
||||
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const LINE_CLAMP = 3;
|
||||
|
||||
const Text = styled.span`
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: ${LINE_CLAMP};
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
text: string;
|
||||
}
|
||||
|
||||
const TruncatedTextComponent: React.FC<Props> = ({ text }) => {
|
||||
return <Text title={text}>{text}</Text>;
|
||||
};
|
||||
|
||||
export const TruncatedText = React.memo(TruncatedTextComponent);
|
|
@ -22,6 +22,7 @@ import {
|
|||
CaseType,
|
||||
OWNER_FIELD,
|
||||
ENABLE_CASE_CONNECTOR,
|
||||
MAX_TITLE_LENGTH,
|
||||
} from '../../../common';
|
||||
import { buildCaseUserActionItem } from '../../services/user_actions/helpers';
|
||||
import { getConnectorFromConfiguration } from '../utils';
|
||||
|
@ -72,6 +73,12 @@ export const create = async (
|
|||
fold(throwErrors(Boom.badRequest), identity)
|
||||
);
|
||||
|
||||
if (query.title.length > MAX_TITLE_LENGTH) {
|
||||
throw Boom.badRequest(
|
||||
`The length of the title is too long. The maximum length is ${MAX_TITLE_LENGTH}.`
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const savedObjectID = SavedObjectsUtils.generateId();
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ import {
|
|||
MAX_CONCURRENT_SEARCHES,
|
||||
SUB_CASE_SAVED_OBJECT,
|
||||
throwErrors,
|
||||
MAX_TITLE_LENGTH,
|
||||
} from '../../../common';
|
||||
import { buildCaseUserActions } from '../../services/user_actions/helpers';
|
||||
import { getCaseToUpdate } from '../utils';
|
||||
|
@ -181,6 +182,24 @@ async function throwIfInvalidUpdateOfTypeWithAlerts({
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an error if any of the requests updates a title and the length is over MAX_TITLE_LENGTH.
|
||||
*/
|
||||
function throwIfTitleIsInvalid(requests: ESCasePatchRequest[]) {
|
||||
const requestsInvalidTitle = requests.filter(
|
||||
(req) => req.title !== undefined && req.title.length > MAX_TITLE_LENGTH
|
||||
);
|
||||
|
||||
if (requestsInvalidTitle.length > 0) {
|
||||
const ids = requestsInvalidTitle.map((req) => req.id);
|
||||
throw Boom.badRequest(
|
||||
`The length of the title is too long. The maximum length is ${MAX_TITLE_LENGTH}, ids: [${ids.join(
|
||||
', '
|
||||
)}]`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id from a reference in a comment for a specific type.
|
||||
*/
|
||||
|
@ -477,6 +496,7 @@ export const update = async (
|
|||
}
|
||||
|
||||
throwIfUpdateOwner(updateFilterCases);
|
||||
throwIfTitleIsInvalid(updateFilterCases);
|
||||
throwIfUpdateStatusOfCollection(updateFilterCases, casesMap);
|
||||
throwIfUpdateTypeCollectionToIndividual(updateFilterCases, casesMap);
|
||||
await throwIfInvalidUpdateOfTypeWithAlerts({
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import 'jest-styled-components';
|
||||
import { createUpdateSuccessToaster } from './helpers';
|
||||
import { Case } from '../../../../../cases/common';
|
||||
|
||||
|
@ -23,12 +26,30 @@ describe('helpers', () => {
|
|||
it('creates the correct toast when the sync alerts is on', () => {
|
||||
// We remove the id as is randomly generated and the text as it is a React component
|
||||
// which is being test on toaster_content.test.tsx
|
||||
const { id, text, ...toast } = createUpdateSuccessToaster(theCase, onViewCaseClick);
|
||||
const { id, text, title, ...toast } = createUpdateSuccessToaster(theCase, onViewCaseClick);
|
||||
const mountedTitle = mount(<>{title}</>);
|
||||
|
||||
expect(toast).toEqual({
|
||||
color: 'success',
|
||||
iconType: 'check',
|
||||
title: 'An alert has been added to "My case"',
|
||||
});
|
||||
expect(mountedTitle).toMatchInlineSnapshot(`
|
||||
.c0 {
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
<styled.span>
|
||||
<span
|
||||
className="c0"
|
||||
>
|
||||
An alert has been added to "My case"
|
||||
</span>
|
||||
</styled.span>
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,11 +7,22 @@
|
|||
|
||||
import React from 'react';
|
||||
import uuid from 'uuid';
|
||||
import styled from 'styled-components';
|
||||
import { AppToast } from '../../../common/components/toasters';
|
||||
import { ToasterContent } from './toaster_content';
|
||||
import * as i18n from './translations';
|
||||
import { Case } from '../../../../../cases/common';
|
||||
|
||||
const LINE_CLAMP = 3;
|
||||
|
||||
const Title = styled.span`
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: ${LINE_CLAMP};
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
export const createUpdateSuccessToaster = (
|
||||
theCase: Case,
|
||||
onViewCaseClick: (id: string) => void
|
||||
|
@ -20,7 +31,7 @@ export const createUpdateSuccessToaster = (
|
|||
id: uuid.v4(),
|
||||
color: 'success',
|
||||
iconType: 'check',
|
||||
title: i18n.CASE_CREATED_SUCCESS_TOAST(theCase.title),
|
||||
title: <Title>{i18n.CASE_CREATED_SUCCESS_TOAST(theCase.title)}</Title>,
|
||||
text: (
|
||||
<ToasterContent
|
||||
caseId={theCase.id}
|
||||
|
|
|
@ -500,6 +500,26 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
expectedHttpCode: 400,
|
||||
});
|
||||
});
|
||||
|
||||
it('400s if the title is too long', async () => {
|
||||
const longTitle =
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nulla enim, rutrum sit amet euismod venenatis, blandit et massa. Nulla id consectetur enim.';
|
||||
|
||||
const postedCase = await createCase(supertest, postCaseReq);
|
||||
await updateCase({
|
||||
supertest,
|
||||
params: {
|
||||
cases: [
|
||||
{
|
||||
id: postedCase.id,
|
||||
version: postedCase.version,
|
||||
title: longTitle,
|
||||
},
|
||||
],
|
||||
},
|
||||
expectedHttpCode: 400,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('alerts', () => {
|
||||
|
|
|
@ -238,6 +238,13 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
.send({ ...req, status: CaseStatuses.open })
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('400s if the title is too long', async () => {
|
||||
const longTitle =
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nulla enim, rutrum sit amet euismod venenatis, blandit et massa. Nulla id consectetur enim.';
|
||||
|
||||
await createCase(supertest, getPostCaseRequest({ title: longTitle }), 400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rbac', () => {
|
||||
|
|
Loading…
Reference in a new issue