Skip to content

Commit

Permalink
[Cases] Guardrails: Total number cases update (#161076)
Browse files Browse the repository at this point in the history
Connected to #146945

## Summary

| Description  | Limit | Done? | Documented?
| ------------- | ---- | :---: | ---- |
| Total number of cases to be updated | 100 | ✅ | Yes |

- Also changed the **minimum** of cases that can be updated to 1.
- Tests.
- Updated Documentation.

### Checklist

Delete any items that are not applicable to this PR.

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

### Release Notes

Patch Cases API is now limited to 100 cases at a time and a minimum of
1.
  • Loading branch information
adcoelho authored Jul 6, 2023
1 parent b7e9dc0 commit 71d96c7
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 82 deletions.
11 changes: 10 additions & 1 deletion x-pack/plugins/cases/common/api/cases/case.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
MAX_ASSIGNEES_FILTER_LENGTH,
MAX_REPORTERS_FILTER_LENGTH,
MAX_TAGS_FILTER_LENGTH,
MAX_CASES_TO_UPDATE,
MAX_BULK_GET_CASES,
MAX_CASES_PER_PAGE,
} from '../../constants';
Expand Down Expand Up @@ -461,7 +462,15 @@ export const CasePatchRequestRt = rt.intersection([
rt.strict({ id: rt.string, version: rt.string }),
]);

export const CasesPatchRequestRt = rt.strict({ cases: rt.array(CasePatchRequestRt) });
export const CasesPatchRequestRt = rt.strict({
cases: limitedArraySchema({
codec: CasePatchRequestRt,
min: 1,
max: MAX_CASES_TO_UPDATE,
fieldName: 'cases',
}),
});

export const CasesRt = rt.array(CaseRt);

export const CasePushRequestParamsRt = rt.strict({
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/cases/common/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export const MAX_COMMENT_LENGTH = 30000 as const;
export const MAX_LENGTH_PER_TAG = 256 as const;
export const MAX_TAGS_PER_CASE = 200 as const;
export const MAX_DELETE_IDS_LENGTH = 100 as const;
export const MAX_CASES_TO_UPDATE = 100 as const;

/**
* Cases features
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/cases/docs/openapi/bundled.json
Original file line number Diff line number Diff line change
Expand Up @@ -5198,6 +5198,8 @@
"cases": {
"type": "array",
"description": "An array containing one or more case objects.",
"maxItems": 100,
"minItems": 1,
"items": {
"type": "object",
"required": [
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/cases/docs/openapi/bundled.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3337,6 +3337,8 @@ components:
cases:
type: array
description: An array containing one or more case objects.
maxItems: 100
minItems: 1
items:
type: object
required:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ properties:
cases:
type: array
description: An array containing one or more case objects.
maxItems: 100
minItems: 1
items:
type: object
required:
Expand Down Expand Up @@ -35,7 +37,7 @@ properties:
settings:
$ref: 'settings.yaml'
severity:
$ref: 'severity_property.yaml'
$ref: 'severity_property.yaml'
status:
$ref: 'status.yaml'
tags:
Expand All @@ -55,4 +57,4 @@ properties:
maxLength: 160
version:
description: The current version of the case. To determine this value, use the get case or find cases APIs.
type: string
type: string
37 changes: 37 additions & 0 deletions x-pack/plugins/cases/server/client/cases/update.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
MAX_TAGS_PER_CASE,
MAX_LENGTH_PER_TAG,
MAX_TITLE_LENGTH,
MAX_CASES_TO_UPDATE,
} from '../../../common/constants';
import { mockCases } from '../../mocks';
import { createCasesClientMockArgs } from '../mocks';
Expand Down Expand Up @@ -701,4 +702,40 @@ describe('update', () => {
);
});
});

describe('Validation', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it(`throws an error when trying to update more than ${MAX_CASES_TO_UPDATE} cases`, async () => {
await expect(
update(
{
cases: Array(MAX_CASES_TO_UPDATE + 1).fill({
id: mockCases[0].id,
version: mockCases[0].version ?? '',
title: 'This is a test case!!',
}),
},
createCasesClientMockArgs()
)
).rejects.toThrow(
'Error: The length of the field cases is too long. Array must be of length <= 100.'
);
});

it('throws an error when trying to update zero cases', async () => {
await expect(
update(
{
cases: [],
},
createCasesClientMockArgs()
)
).rejects.toThrow(
'Error: The length of the field cases is too short. Array must be of length >= 1.'
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -561,109 +561,133 @@ export default ({ getService }: FtrProviderContext): void => {
});
});

it('400s if the title is too long', async () => {
const longTitle = 'a'.repeat(161);

const postedCase = await createCase(supertest, postCaseReq);
it('400s when trying to update too many cases', async () => {
await updateCase({
supertest,
params: {
cases: [
{
id: postedCase.id,
version: postedCase.version,
title: longTitle,
},
],
cases: Array(101).fill({ id: 'foo', version: 'bar', title: 'coolTitle' }),
},
expectedHttpCode: 400,
});
});

it('400s if the title an empty string', async () => {
const postedCase = await createCase(supertest, postCaseReq);
it('400s when trying to update zero cases', async () => {
await updateCase({
supertest,
params: {
cases: [
{
id: postedCase.id,
version: postedCase.version,
title: '',
},
],
cases: [],
},
expectedHttpCode: 400,
});
});

it('400s if the title is a string with empty characters', async () => {
const postedCase = await createCase(supertest, postCaseReq);
await updateCase({
supertest,
params: {
cases: [
{
id: postedCase.id,
version: postedCase.version,
title: ' ',
},
],
},
expectedHttpCode: 400,
describe('title', async () => {
it('400s if the title is too long', async () => {
const longTitle = 'a'.repeat(161);

const postedCase = await createCase(supertest, postCaseReq);
await updateCase({
supertest,
params: {
cases: [
{
id: postedCase.id,
version: postedCase.version,
title: longTitle,
},
],
},
expectedHttpCode: 400,
});
});
});

it('400s if the description is too long', async () => {
const longDescription = 'a'.repeat(30001);
it('400s if the title an empty string', async () => {
const postedCase = await createCase(supertest, postCaseReq);
await updateCase({
supertest,
params: {
cases: [
{
id: postedCase.id,
version: postedCase.version,
title: '',
},
],
},
expectedHttpCode: 400,
});
});

const postedCase = await createCase(supertest, postCaseReq);
await updateCase({
supertest,
params: {
cases: [
{
id: postedCase.id,
version: postedCase.version,
description: longDescription,
},
],
},
expectedHttpCode: 400,
it('400s if the title is a string with empty characters', async () => {
const postedCase = await createCase(supertest, postCaseReq);
await updateCase({
supertest,
params: {
cases: [
{
id: postedCase.id,
version: postedCase.version,
title: ' ',
},
],
},
expectedHttpCode: 400,
});
});
});

it('400s if the description an empty string', async () => {
const postedCase = await createCase(supertest, postCaseReq);
await updateCase({
supertest,
params: {
cases: [
{
id: postedCase.id,
version: postedCase.version,
description: '',
},
],
},
expectedHttpCode: 400,
describe('description', async () => {
it('400s if the description is too long', async () => {
const longDescription = 'a'.repeat(30001);

const postedCase = await createCase(supertest, postCaseReq);
await updateCase({
supertest,
params: {
cases: [
{
id: postedCase.id,
version: postedCase.version,
description: longDescription,
},
],
},
expectedHttpCode: 400,
});
});
});

it('400s if the description is a string with empty characters', async () => {
const postedCase = await createCase(supertest, postCaseReq);
await updateCase({
supertest,
params: {
cases: [
{
id: postedCase.id,
version: postedCase.version,
description: ' ',
},
],
},
expectedHttpCode: 400,
it('400s if the description an empty string', async () => {
const postedCase = await createCase(supertest, postCaseReq);
await updateCase({
supertest,
params: {
cases: [
{
id: postedCase.id,
version: postedCase.version,
description: '',
},
],
},
expectedHttpCode: 400,
});
});

it('400s if the description is a string with empty characters', async () => {
const postedCase = await createCase(supertest, postCaseReq);
await updateCase({
supertest,
params: {
cases: [
{
id: postedCase.id,
version: postedCase.version,
description: ' ',
},
],
},
expectedHttpCode: 400,
});
});
});

Expand Down

0 comments on commit 71d96c7

Please sign in to comment.