Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.1] [Security Solution][Lists] - Fix exception list with comments import bug (#124909) #125807

Merged
merged 1 commit into from
Feb 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { pipe } from 'fp-ts/lib/pipeable';
import { left } from 'fp-ts/lib/Either';
import { foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils';
import { ImportCommentsArray } from '../import_comment';
import { DefaultImportCommentsArray } from '../default_import_comments_array';
import { getCommentsArrayMock } from '../comment/index.mock';
import { getCreateCommentsArrayMock } from '../create_comment/index.mock';

describe('default_import_comments_array', () => {
test('it should pass validation when supplied an empty array', () => {
const payload: ImportCommentsArray = [];
const decoded = DefaultImportCommentsArray.decode(payload);
const message = pipe(decoded, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
});

test('it should pass validation when supplied an array of comments', () => {
const payload: ImportCommentsArray = getCommentsArrayMock();
const decoded = DefaultImportCommentsArray.decode(payload);
const message = pipe(decoded, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
});

test('it should pass validation when supplied an array of new comments', () => {
const payload: ImportCommentsArray = getCreateCommentsArrayMock();
const decoded = DefaultImportCommentsArray.decode(payload);
const message = pipe(decoded, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
});

test('it should pass validation when supplied an array of new and existing comments', () => {
const payload: ImportCommentsArray = [
...getCommentsArrayMock(),
...getCreateCommentsArrayMock(),
];
const decoded = DefaultImportCommentsArray.decode(payload);
const message = pipe(decoded, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
});

test('it should fail validation when supplied an array of numbers', () => {
const payload = [1];
const decoded = DefaultImportCommentsArray.decode(payload);
const message = pipe(decoded, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual([
'Invalid value "1" supplied to "DefaultImportComments"',
]);
expect(message.schema).toEqual({});
});

test('it should fail validation when supplied an array of strings', () => {
const payload = ['some string'];
const decoded = DefaultImportCommentsArray.decode(payload);
const message = pipe(decoded, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual([
'Invalid value "some string" supplied to "DefaultImportComments"',
]);
expect(message.schema).toEqual({});
});

test('it should return a default array entry', () => {
const payload = null;
const decoded = DefaultImportCommentsArray.decode(payload);
const message = pipe(decoded, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual([]);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import * as t from 'io-ts';
import { Either } from 'fp-ts/lib/Either';
import { importComment, ImportCommentsArray } from '../import_comment';

/**
* Types the DefaultImportCommentsArray as:
* - If null or undefined, then a default array of type ImportCommentsArray will be set
*/
export const DefaultImportCommentsArray = new t.Type<
ImportCommentsArray,
ImportCommentsArray,
unknown
>(
'DefaultImportComments',
t.array(importComment).is,
(input, context): Either<t.Errors, ImportCommentsArray> =>
input == null ? t.success([]) : t.array(importComment).validate(input, context),
t.identity
);
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe('default_update_comments_array', () => {
const message = pipe(decoded, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual([
'Invalid value "1" supplied to "Array<({| comment: NonEmptyString |} & Partial<{| id: NonEmptyString |}>)>"',
'Invalid value "1" supplied to "DefaultUpdateComments"',
]);
expect(message.schema).toEqual({});
});
Expand All @@ -49,7 +49,7 @@ describe('default_update_comments_array', () => {
const message = pipe(decoded, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual([
'Invalid value "some string" supplied to "Array<({| comment: NonEmptyString |} & Partial<{| id: NonEmptyString |}>)>"',
'Invalid value "some string" supplied to "DefaultUpdateComments"',
]);
expect(message.schema).toEqual({});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ import { Either } from 'fp-ts/lib/Either';
import { updateCommentsArray, UpdateCommentsArray } from '../update_comment';

/**
* Types the DefaultCommentsUpdate as:
* - If null or undefined, then a default array of type entry will be set
* Types the DefaultUpdateComments as:
* - If null or undefined, then a default array of type UpdateCommentsArray will be set
*/
export const DefaultUpdateCommentsArray = new t.Type<
UpdateCommentsArray,
UpdateCommentsArray,
unknown
>(
'DefaultCreateComments',
'DefaultUpdateComments',
updateCommentsArray.is,
(input): Either<t.Errors, UpdateCommentsArray> =>
input == null ? t.success([]) : updateCommentsArray.decode(input),
(input, context): Either<t.Errors, UpdateCommentsArray> =>
input == null ? t.success([]) : updateCommentsArray.validate(input, context),
t.identity
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { pipe } from 'fp-ts/lib/pipeable';
import { left } from 'fp-ts/lib/Either';
import { getCommentsArrayMock, getCommentsMock } from '../comment/index.mock';
import { getCreateCommentsArrayMock } from '../create_comment/index.mock';
import {
importComment,
ImportCommentsArray,
importCommentsArray,
ImportCommentsArrayOrUndefined,
importCommentsArrayOrUndefined,
} from '.';
import { foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils';

describe('ImportComment', () => {
describe('importComment', () => {
test('it passes validation with a typical comment', () => {
const payload = getCommentsMock();
const decoded = importComment.decode(payload);
const message = pipe(decoded, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
});

test('it passes validation with a new comment', () => {
const payload = { comment: 'new comment' };
const decoded = importComment.decode(payload);
const message = pipe(decoded, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
});

test('it fails validation when undefined', () => {
const payload = undefined;
const decoded = importComment.decode(payload);
const message = pipe(decoded, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual([
'Invalid value "undefined" supplied to "(({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: NonEmptyString |})"',
]);
expect(message.schema).toEqual({});
});
});

describe('importCommentsArray', () => {
test('it passes validation an array of Comment', () => {
const payload = getCommentsArrayMock();
const decoded = importCommentsArray.decode(payload);
const message = pipe(decoded, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
});

test('it passes validation an array of CreateComment', () => {
const payload = getCreateCommentsArrayMock();
const decoded = importCommentsArray.decode(payload);
const message = pipe(decoded, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
});

test('it passes validation an array of Comment and CreateComment', () => {
const payload = [...getCommentsArrayMock(), ...getCreateCommentsArrayMock()];
const decoded = importCommentsArray.decode(payload);
const message = pipe(decoded, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
});

test('it fails validation when undefined', () => {
const payload = undefined;
const decoded = importCommentsArray.decode(payload);
const message = pipe(decoded, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual([
'Invalid value "undefined" supplied to "Array<(({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: NonEmptyString |})>"',
]);
expect(message.schema).toEqual({});
});

test('it fails validation when array includes non ImportComment types', () => {
const payload = [1] as unknown as ImportCommentsArray;
const decoded = importCommentsArray.decode(payload);
const message = pipe(decoded, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual([
'Invalid value "1" supplied to "Array<(({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: NonEmptyString |})>"',
]);
expect(message.schema).toEqual({});
});
});

describe('importCommentsArrayOrUndefined', () => {
test('it passes validation an array of ImportComment', () => {
const payload = [...getCommentsArrayMock(), ...getCreateCommentsArrayMock()];
const decoded = importCommentsArrayOrUndefined.decode(payload);
const message = pipe(decoded, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
});

test('it passes validation when undefined', () => {
const payload = undefined;
const decoded = importCommentsArrayOrUndefined.decode(payload);
const message = pipe(decoded, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
});

test('it fails validation when array includes non ImportComment types', () => {
const payload = [1] as unknown as ImportCommentsArrayOrUndefined;
const decoded = importCommentsArray.decode(payload);
const message = pipe(decoded, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual([
'Invalid value "1" supplied to "Array<(({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: NonEmptyString |})>"',
]);
expect(message.schema).toEqual({});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import * as t from 'io-ts';
import { createComment } from '../create_comment';
import { comment } from '../comment';

export const importComment = t.union([comment, createComment]);

export type ImportComment = t.TypeOf<typeof importComment>;
export const importCommentsArray = t.array(importComment);
export type ImportCommentsArray = t.TypeOf<typeof importCommentsArray>;
export const importCommentsArrayOrUndefined = t.union([importCommentsArray, t.undefined]);
export type ImportCommentsArrayOrUndefined = t.TypeOf<typeof importCommentsArrayOrUndefined>;
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export * from './created_by';
export * from './cursor';
export * from './default_namespace';
export * from './default_namespace_array';
export * from './default_import_comments_array';
export * from './description';
export * from './deserializer';
export * from './endpoint';
Expand All @@ -29,6 +30,7 @@ export * from './exception_list_item_type';
export * from './filter';
export * from './id';
export * from './immutable';
export * from './import_comment';
export * from './item_id';
export * from './list_id';
export * from './list_operator';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
} from '.';
import { foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils';

describe('CommentsUpdate', () => {
describe('UpdateComment', () => {
describe('updateComment', () => {
test('it should pass validation when supplied typical comment update', () => {
const payload = getUpdateCommentMock();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
getImportExceptionsListItemSchemaDecodedMock,
getImportExceptionsListItemSchemaMock,
} from './index.mock';
import { getCommentsArrayMock } from '../../common/comment/index.mock';

describe('import_list_item_schema', () => {
test('it should validate a typical item request', () => {
Expand All @@ -27,6 +28,35 @@ describe('import_list_item_schema', () => {
expect(message.schema).toEqual(getImportExceptionsListItemSchemaDecodedMock());
});

test('it should validate a typical item request with comments', () => {
const payload = {
...getImportExceptionsListItemSchemaMock(),
comments: getCommentsArrayMock(),
};
const decoded = importExceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual({
...getImportExceptionsListItemSchemaDecodedMock(),
comments: [
{
comment: 'some old comment',
created_at: '2020-04-20T15:25:31.830Z',
created_by: 'some user',
id: 'uuid_here',
},
{
comment: 'some old comment',
created_at: '2020-04-20T15:25:31.830Z',
created_by: 'some user',
id: 'uuid_here',
},
],
});
});

test('it should NOT accept an undefined for "item_id"', () => {
const payload: Partial<ReturnType<typeof getImportExceptionsListItemSchemaMock>> =
getImportExceptionsListItemSchemaMock();
Expand Down
Loading