diff --git a/x-pack/plugins/lists/common/constants.mock.ts b/x-pack/plugins/lists/common/constants.mock.ts index b7609b5a3602a..428cc90d2908b 100644 --- a/x-pack/plugins/lists/common/constants.mock.ts +++ b/x-pack/plugins/lists/common/constants.mock.ts @@ -9,6 +9,7 @@ import { EntriesArray } from './schemas/types'; export const DATE_NOW = '2020-04-20T15:25:31.830Z'; export const OLD_DATE_RELATIVE_TO_DATE_NOW = '2020-04-19T15:25:31.830Z'; export const USER = 'some user'; +export const ELASTIC_USER = 'elastic'; export const LIST_INDEX = '.lists'; export const LIST_ITEM_INDEX = '.items'; export const NAME = 'some name'; diff --git a/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.mock.ts index f0d4af520bdbb..5a9e50554865b 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.mock.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.mock.ts @@ -14,3 +14,20 @@ export const getCreateListItemSchemaMock = (): CreateListItemSchema => ({ meta: META, value: VALUE, }); + +/** + * Useful for end to end testing + */ +export const getCreateMinimalListItemSchemaMock = (): CreateListItemSchema => ({ + id: LIST_ITEM_ID, + list_id: LIST_ID, + value: VALUE, +}); + +/** + * Useful for end to end testing + */ +export const getCreateMinimalListItemSchemaMockWithoutId = (): CreateListItemSchema => ({ + list_id: LIST_ID, + value: VALUE, +}); diff --git a/x-pack/plugins/lists/common/schemas/request/create_list_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/create_list_schema.mock.ts index 461890b944bfa..194625c09fb79 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_list_schema.mock.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_list_schema.mock.ts @@ -18,3 +18,22 @@ export const getCreateListSchemaMock = (): CreateListSchema => ({ type: TYPE, version: VERSION, }); + +/** + * Useful for end to end tests and other mechanisms which want to fill in the values + */ +export const getCreateMinimalListSchemaMock = (): CreateListSchema => ({ + description: DESCRIPTION, + id: LIST_ID, + name: NAME, + type: TYPE, +}); + +/** + * Useful for end to end tests and other mechanisms which want to fill in the values + */ +export const getCreateMinimalListSchemaMockWithoutId = (): CreateListSchema => ({ + description: DESCRIPTION, + name: NAME, + type: TYPE, +}); diff --git a/x-pack/plugins/lists/common/schemas/request/import_list_item_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/import_list_item_schema.mock.ts index 69e4d2f8293c7..57bb66f746f67 100644 --- a/x-pack/plugins/lists/common/schemas/request/import_list_item_schema.mock.ts +++ b/x-pack/plugins/lists/common/schemas/request/import_list_item_schema.mock.ts @@ -9,3 +9,12 @@ import { ImportListItemSchema } from './import_list_item_schema'; export const getImportListItemSchemaMock = (): ImportListItemSchema => ({ file: {}, }); + +/** + * This is useful for end to end tests, it will return a buffer given a string array + * of things to import. + * @param input Array of strings of things to import + */ +export const getImportListItemAsBuffer = (input: string[]): Buffer => { + return Buffer.from(input.join('\r\n')); +}; diff --git a/x-pack/plugins/lists/common/schemas/request/update_list_item_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/update_list_item_schema.mock.ts index c95de1e99b4f0..be8ba3516a754 100644 --- a/x-pack/plugins/lists/common/schemas/request/update_list_item_schema.mock.ts +++ b/x-pack/plugins/lists/common/schemas/request/update_list_item_schema.mock.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ID, META, VALUE } from '../../constants.mock'; +import { ID, LIST_ITEM_ID, META, VALUE } from '../../constants.mock'; import { UpdateListItemSchema } from './update_list_item_schema'; @@ -13,3 +13,11 @@ export const getUpdateListItemSchemaMock = (): UpdateListItemSchema => ({ meta: META, value: VALUE, }); + +/** + * Useful for end to end testing + */ +export const getUpdateMinimalListItemSchemaMock = (): UpdateListItemSchema => ({ + id: LIST_ITEM_ID, + value: VALUE, +}); diff --git a/x-pack/plugins/lists/common/schemas/request/update_list_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/update_list_schema.mock.ts new file mode 100644 index 0000000000000..b044d40a5d88f --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/update_list_schema.mock.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { DESCRIPTION, LIST_ID, META, NAME, _VERSION } from '../../constants.mock'; + +import { UpdateListSchema } from './update_list_schema'; + +export const getUpdateListSchemaMock = (): UpdateListSchema => ({ + _version: _VERSION, + description: DESCRIPTION, + id: LIST_ID, + meta: META, + name: NAME, +}); + +/** + * Useful for end to end tests and other mechanisms which want to fill in the values + * after doing a get of the structure. + */ +export const getUpdateMinimalListSchemaMock = (): UpdateListSchema => ({ + description: DESCRIPTION, + id: LIST_ID, + name: NAME, +}); diff --git a/x-pack/plugins/lists/common/schemas/request/update_list_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/update_list_schema.test.ts new file mode 100644 index 0000000000000..21d20a6b85bce --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/update_list_schema.test.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { left } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; + +import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; + +import { UpdateListSchema, updateListSchema } from './update_list_schema'; +import { getUpdateListSchemaMock } from './update_list_schema.mock'; + +describe('update_list_schema', () => { + test('it should validate a typical list request', () => { + const payload = getUpdateListSchemaMock(); + const decoded = updateListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for "meta" but strip it out', () => { + const payload = getUpdateListSchemaMock(); + const outputPayload = getUpdateListSchemaMock(); + delete payload.meta; + const decoded = updateListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + delete outputPayload.meta; + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(outputPayload); + }); + + test('it should not allow an extra key to be sent in', () => { + const payload: UpdateListSchema & { + extraKey?: string; + } = getUpdateListSchemaMock(); + payload.extraKey = 'some new value'; + const decoded = updateListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "extraKey"']); + expect(message.schema).toEqual({}); + }); +}); diff --git a/x-pack/plugins/lists/common/schemas/response/list_item_schema.mock.ts b/x-pack/plugins/lists/common/schemas/response/list_item_schema.mock.ts index e122f6a2bbe3b..f4e36d1d060c0 100644 --- a/x-pack/plugins/lists/common/schemas/response/list_item_schema.mock.ts +++ b/x-pack/plugins/lists/common/schemas/response/list_item_schema.mock.ts @@ -7,6 +7,7 @@ import { ListItemSchema } from '../../../common/schemas'; import { DATE_NOW, + ELASTIC_USER, LIST_ID, LIST_ITEM_ID, META, @@ -31,3 +32,15 @@ export const getListItemResponseMock = (): ListItemSchema => ({ updated_by: USER, value: VALUE, }); + +/** + * This is useful for end to end tests where we remove the auto generated parts for comparisons + * such as created_at, updated_at, and id. + */ +export const getListItemResponseMockWithoutAutoGeneratedValues = (): Partial => ({ + created_by: ELASTIC_USER, + list_id: LIST_ID, + type: TYPE, + updated_by: ELASTIC_USER, + value: VALUE, +}); diff --git a/x-pack/plugins/lists/common/schemas/response/list_schema.mock.ts b/x-pack/plugins/lists/common/schemas/response/list_schema.mock.ts index 900c7ea4322a3..4ae77e12a8294 100644 --- a/x-pack/plugins/lists/common/schemas/response/list_schema.mock.ts +++ b/x-pack/plugins/lists/common/schemas/response/list_schema.mock.ts @@ -8,6 +8,7 @@ import { ListSchema } from '../../../common/schemas'; import { DATE_NOW, DESCRIPTION, + ELASTIC_USER, IMMUTABLE, LIST_ID, META, @@ -35,3 +36,17 @@ export const getListResponseMock = (): ListSchema => ({ updated_by: USER, version: VERSION, }); + +/** + * This is useful for end to end tests where we remove the auto generated parts for comparisons + * such as created_at, updated_at, and id. + */ +export const getListResponseMockWithoutAutoGeneratedValues = (): Partial => ({ + created_by: ELASTIC_USER, + description: DESCRIPTION, + immutable: IMMUTABLE, + name: NAME, + type: TYPE, + updated_by: ELASTIC_USER, + version: VERSION, +}); diff --git a/x-pack/plugins/lists/server/routes/create_list_item_route.ts b/x-pack/plugins/lists/server/routes/create_list_item_route.ts index 0a4a1c739ae7c..bd2828d331d83 100644 --- a/x-pack/plugins/lists/server/routes/create_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/create_list_item_route.ts @@ -36,6 +36,15 @@ export const createListItemRoute = (router: IRouter): void => { statusCode: 404, }); } else { + if (id != null) { + const listItem = await lists.getListItem({ id }); + if (listItem != null) { + return siemResponse.error({ + body: `list item id: "${id}" already exists`, + statusCode: 409, + }); + } + } const createdListItem = await lists.createListItem({ deserializer: list.deserializer, id, diff --git a/x-pack/plugins/lists/server/routes/delete_list_item_route.ts b/x-pack/plugins/lists/server/routes/delete_list_item_route.ts index 2284068552485..fa1adf8a39ed8 100644 --- a/x-pack/plugins/lists/server/routes/delete_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/delete_list_item_route.ts @@ -33,7 +33,7 @@ export const deleteListItemRoute = (router: IRouter): void => { const deleted = await lists.deleteListItem({ id }); if (deleted == null) { return siemResponse.error({ - body: `list item with id: "${id}" item not found`, + body: `list item with id: "${id}" not found`, statusCode: 404, }); } else { diff --git a/x-pack/plugins/lists/server/routes/import_list_item_route.ts b/x-pack/plugins/lists/server/routes/import_list_item_route.ts index ce5fdaccae251..d46c943d95fe9 100644 --- a/x-pack/plugins/lists/server/routes/import_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/import_list_item_route.ts @@ -41,6 +41,13 @@ export const importListItemRoute = (router: IRouter, config: ConfigType): void = const stream = createStreamFromBuffer(request.body); const { deserializer, list_id: listId, serializer, type } = request.query; const lists = getListClient(context); + const listExists = await lists.getListIndexExists(); + if (!listExists) { + return siemResponse.error({ + body: `To import a list item, the index must exist first. Index "${lists.getListIndex()}" does not exist`, + statusCode: 400, + }); + } if (listId != null) { const list = await lists.getList({ id: listId }); if (list == null) { diff --git a/x-pack/plugins/lists/server/routes/patch_list_route.ts b/x-pack/plugins/lists/server/routes/patch_list_route.ts index e33d8d7c9c598..763f3f495ca17 100644 --- a/x-pack/plugins/lists/server/routes/patch_list_route.ts +++ b/x-pack/plugins/lists/server/routes/patch_list_route.ts @@ -32,7 +32,7 @@ export const patchListRoute = (router: IRouter): void => { const list = await lists.updateList({ _version, description, id, meta, name, version }); if (list == null) { return siemResponse.error({ - body: `list id: "${id}" found found`, + body: `list id: "${id}" not found`, statusCode: 404, }); } else { diff --git a/x-pack/plugins/lists/server/routes/update_exception_list_route.ts b/x-pack/plugins/lists/server/routes/update_exception_list_route.ts index bead10802df4f..8102210b8430d 100644 --- a/x-pack/plugins/lists/server/routes/update_exception_list_route.ts +++ b/x-pack/plugins/lists/server/routes/update_exception_list_route.ts @@ -69,7 +69,7 @@ export const updateExceptionListRoute = (router: IRouter): void => { }); if (list == null) { return siemResponse.error({ - body: `exception list id: "${id}" found found`, + body: `exception list id: "${id}" not found`, statusCode: 404, }); } else { diff --git a/x-pack/plugins/lists/server/routes/update_list_route.ts b/x-pack/plugins/lists/server/routes/update_list_route.ts index 816ad13d3770e..8d7d08be4130b 100644 --- a/x-pack/plugins/lists/server/routes/update_list_route.ts +++ b/x-pack/plugins/lists/server/routes/update_list_route.ts @@ -32,7 +32,7 @@ export const updateListRoute = (router: IRouter): void => { const list = await lists.updateList({ _version, description, id, meta, name, version }); if (list == null) { return siemResponse.error({ - body: `list id: "${id}" found found`, + body: `list id: "${id}" not found`, statusCode: 404, }); } else { diff --git a/x-pack/test/detection_engine_api_integration/common/config.ts b/x-pack/test/detection_engine_api_integration/common/config.ts index bb9b3d9e96664..46fb877e94f23 100644 --- a/x-pack/test/detection_engine_api_integration/common/config.ts +++ b/x-pack/test/detection_engine_api_integration/common/config.ts @@ -73,7 +73,6 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) ])}`, `--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`, '--xpack.eventLog.logEntries=true', - '--xpack.lists.enabled=true', ...disabledPlugins.map((key) => `--xpack.${key}.enabled=false`), `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'alerts')}`, `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'actions')}`, diff --git a/x-pack/test/lists_api_integration/common/config.ts b/x-pack/test/lists_api_integration/common/config.ts new file mode 100644 index 0000000000000..ca3cc3a5eee2f --- /dev/null +++ b/x-pack/test/lists_api_integration/common/config.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import path from 'path'; +import { CA_CERT_PATH } from '@kbn/dev-utils'; +import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; +import { services } from './services'; + +interface CreateTestConfigOptions { + license: string; + disabledPlugins?: string[]; + ssl?: boolean; +} + +export function createTestConfig(name: string, options: CreateTestConfigOptions) { + const { license = 'trial', disabledPlugins = [], ssl = false } = options; + + return async ({ readConfigFile }: FtrConfigProviderContext) => { + const xPackApiIntegrationTestsConfig = await readConfigFile( + require.resolve('../../api_integration/config.ts') + ); + const servers = { + ...xPackApiIntegrationTestsConfig.get('servers'), + elasticsearch: { + ...xPackApiIntegrationTestsConfig.get('servers.elasticsearch'), + protocol: ssl ? 'https' : 'http', + }, + }; + + return { + testFiles: [require.resolve(`../${name}/tests/`)], + servers, + services, + junit: { + reportName: 'X-Pack Lists Integration Tests', + }, + esArchiver: xPackApiIntegrationTestsConfig.get('esArchiver'), + esTestCluster: { + ...xPackApiIntegrationTestsConfig.get('esTestCluster'), + license, + ssl, + serverArgs: [ + `xpack.license.self_generated.type=${license}`, + `xpack.security.enabled=${!disabledPlugins.includes('security')}`, + ], + }, + kbnTestServer: { + ...xPackApiIntegrationTestsConfig.get('kbnTestServer'), + serverArgs: [ + ...xPackApiIntegrationTestsConfig.get('kbnTestServer.serverArgs'), + ...disabledPlugins.map((key) => `--xpack.${key}.enabled=false`), + `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'alerts')}`, + `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'actions')}`, + `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'task_manager')}`, + `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'aad')}`, + ...(ssl + ? [ + `--elasticsearch.hosts=${servers.elasticsearch.protocol}://${servers.elasticsearch.hostname}:${servers.elasticsearch.port}`, + `--elasticsearch.ssl.certificateAuthorities=${CA_CERT_PATH}`, + ] + : []), + ], + }, + }; + }; +} diff --git a/x-pack/test/lists_api_integration/common/ftr_provider_context.d.ts b/x-pack/test/lists_api_integration/common/ftr_provider_context.d.ts new file mode 100644 index 0000000000000..e3add3748f56d --- /dev/null +++ b/x-pack/test/lists_api_integration/common/ftr_provider_context.d.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { GenericFtrProviderContext } from '@kbn/test/types/ftr'; + +import { services } from './services'; + +export type FtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/lists_api_integration/common/services.ts b/x-pack/test/lists_api_integration/common/services.ts new file mode 100644 index 0000000000000..a927a31469bab --- /dev/null +++ b/x-pack/test/lists_api_integration/common/services.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { services } from '../../api_integration/services'; diff --git a/x-pack/test/lists_api_integration/security_and_spaces/config.ts b/x-pack/test/lists_api_integration/security_and_spaces/config.ts new file mode 100644 index 0000000000000..081b901c47fc3 --- /dev/null +++ b/x-pack/test/lists_api_integration/security_and_spaces/config.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { createTestConfig } from '../common/config'; + +// eslint-disable-next-line import/no-default-export +export default createTestConfig('security_and_spaces', { + disabledPlugins: [], + license: 'trial', + ssl: true, +}); diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/create_list_items.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/create_list_items.ts new file mode 100644 index 0000000000000..906e9a9ef4456 --- /dev/null +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/create_list_items.ts @@ -0,0 +1,115 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +import { LIST_URL, LIST_ITEM_URL } from '../../../../plugins/lists/common/constants'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +import { getCreateMinimalListSchemaMock } from '../../../../plugins/lists/common/schemas/request/create_list_schema.mock'; + +import { + getCreateMinimalListItemSchemaMock, + getCreateMinimalListItemSchemaMockWithoutId, +} from '../../../../plugins/lists/common/schemas/request/create_list_item_schema.mock'; +import { getListItemResponseMockWithoutAutoGeneratedValues } from '../../../../plugins/lists/common/schemas/response/list_item_schema.mock'; + +import { + createListsIndex, + deleteListsIndex, + removeListItemServerGeneratedProperties, +} from '../../utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + + describe('create_list_items', () => { + describe('validation errors', () => { + it('should give a 404 error that the list must exist first before being able to add a list item', async () => { + const { body } = await supertest + .post(LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListItemSchemaMock()) + .expect(404); + + expect(body).to.eql({ + message: 'list id: "some-list-id" does not exist', + status_code: 404, + }); + }); + }); + + describe('creating list items', () => { + beforeEach(async () => { + await createListsIndex(supertest); + }); + + afterEach(async () => { + await deleteListsIndex(supertest); + }); + + it('should create a simple list item with a list item id', async () => { + await supertest + .post(LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListSchemaMock()) + .expect(200); + + const { body } = await supertest + .post(LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListItemSchemaMock()) + .expect(200); + + const bodyToCompare = removeListItemServerGeneratedProperties(body); + expect(bodyToCompare).to.eql(getListItemResponseMockWithoutAutoGeneratedValues()); + }); + + it('should create a simple list item without an id', async () => { + await supertest + .post(LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListSchemaMock()) + .expect(200); + + const { body } = await supertest + .post(LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListItemSchemaMockWithoutId()) + .expect(200); + + const bodyToCompare = removeListItemServerGeneratedProperties(body); + expect(bodyToCompare).to.eql(getListItemResponseMockWithoutAutoGeneratedValues()); + }); + + it('should cause a 409 conflict if we attempt to create the same list item twice', async () => { + await supertest + .post(LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListSchemaMock()) + .expect(200); + + await supertest + .post(LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListItemSchemaMock()) + .expect(200); + + const { body } = await supertest + .post(LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListItemSchemaMock()) + .expect(409); + + expect(body).to.eql({ + message: 'list item id: "some-list-item-id" already exists', + status_code: 409, + }); + }); + }); + }); +}; diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/create_lists.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/create_lists.ts new file mode 100644 index 0000000000000..b9d61eeacfee8 --- /dev/null +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/create_lists.ts @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +import { LIST_URL } from '../../../../plugins/lists/common/constants'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { + getCreateMinimalListSchemaMock, + getCreateMinimalListSchemaMockWithoutId, +} from '../../../../plugins/lists/common/schemas/request/create_list_schema.mock'; +import { getListResponseMockWithoutAutoGeneratedValues } from '../../../../plugins/lists/common/schemas/response/list_schema.mock'; + +import { + createListsIndex, + deleteListsIndex, + removeListServerGeneratedProperties, +} from '../../utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + + describe('create_lists', () => { + describe('validation errors', () => { + it('should give an error that the index must exist first if it does not exist before creating a list', async () => { + const { body } = await supertest + .post(LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListSchemaMock()) + .expect(400); + + expect(body).to.eql({ + message: + 'To create a list, the index must exist first. Index ".lists-default" does not exist', + status_code: 400, + }); + }); + }); + + describe('creating lists', () => { + beforeEach(async () => { + await createListsIndex(supertest); + }); + + afterEach(async () => { + await deleteListsIndex(supertest); + }); + + it('should create a simple list with a list_id', async () => { + const { body } = await supertest + .post(LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListSchemaMock()) + .expect(200); + + const bodyToCompare = removeListServerGeneratedProperties(body); + expect(bodyToCompare).to.eql(getListResponseMockWithoutAutoGeneratedValues()); + }); + + it('should create a simple list without a list_id', async () => { + const { body } = await supertest + .post(LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListSchemaMockWithoutId()) + .expect(200); + + const bodyToCompare = removeListServerGeneratedProperties(body); + expect(bodyToCompare).to.eql(getListResponseMockWithoutAutoGeneratedValues()); + }); + + it('should cause a 409 conflict if we attempt to create the same list_id twice', async () => { + await supertest + .post(LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListSchemaMock()) + .expect(200); + + const { body } = await supertest + .post(LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListSchemaMock()) + .expect(409); + + expect(body).to.eql({ + message: 'list id: "some-list-id" already exists', + status_code: 409, + }); + }); + }); + }); +}; diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/delete_list_items.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/delete_list_items.ts new file mode 100644 index 0000000000000..83ba9728efdc9 --- /dev/null +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/delete_list_items.ts @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +import { getListItemResponseMockWithoutAutoGeneratedValues } from '../../../../plugins/lists/common/schemas/response/list_item_schema.mock'; +import { getCreateMinimalListItemSchemaMock } from '../../../../plugins/lists/common/schemas/request/create_list_item_schema.mock'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { LIST_URL, LIST_ITEM_URL } from '../../../../plugins/lists/common/constants'; + +import { getCreateMinimalListSchemaMock } from '../../../../plugins/lists/common/schemas/request/create_list_schema.mock'; +import { + createListsIndex, + deleteListsIndex, + removeListItemServerGeneratedProperties, +} from '../../utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + + describe('delete_list_items', () => { + describe('deleting list items', () => { + beforeEach(async () => { + await createListsIndex(supertest); + }); + + afterEach(async () => { + await deleteListsIndex(supertest); + }); + + it('should delete a single list item with a list item id', async () => { + // create a list + await supertest + .post(LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListSchemaMock()) + .expect(200); + + // create a list item + await supertest + .post(LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListItemSchemaMock()) + .expect(200); + + // delete the list item by its list item id + const { body } = await supertest + .delete(`${LIST_ITEM_URL}?id=${getCreateMinimalListItemSchemaMock().id}`) + .set('kbn-xsrf', 'true') + .expect(200); + + const bodyToCompare = removeListItemServerGeneratedProperties(body); + expect(bodyToCompare).to.eql(getListItemResponseMockWithoutAutoGeneratedValues()); + }); + + it('should delete a single list using an auto generated id', async () => { + // create a list + await supertest + .post(LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListSchemaMock()) + .expect(200); + + // create a list item + const { body: bodyWithCreateListItem } = await supertest + .post(LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListItemSchemaMock()) + .expect(200); + + // delete that list by its auto-generated id + const { body } = await supertest + .delete(`${LIST_ITEM_URL}?id=${bodyWithCreateListItem.id}`) + .set('kbn-xsrf', 'true') + .expect(200); + + const bodyToCompare = removeListItemServerGeneratedProperties(body); + expect(bodyToCompare).to.eql(getListItemResponseMockWithoutAutoGeneratedValues()); + }); + + it('should return an error if the id does not exist when trying to delete it', async () => { + const { body } = await supertest + .delete(`${LIST_ITEM_URL}?id=c1e1b359-7ac1-4e96-bc81-c683c092436f`) + .set('kbn-xsrf', 'true') + .expect(404); + + expect(body).to.eql({ + message: 'list item with id: "c1e1b359-7ac1-4e96-bc81-c683c092436f" not found', + status_code: 404, + }); + }); + }); + }); +}; diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/delete_lists.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/delete_lists.ts new file mode 100644 index 0000000000000..3703e1b6ca306 --- /dev/null +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/delete_lists.ts @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { LIST_URL } from '../../../../plugins/lists/common/constants'; + +import { getCreateMinimalListSchemaMock } from '../../../../plugins/lists/common/schemas/request/create_list_schema.mock'; +import { + createListsIndex, + deleteListsIndex, + removeListServerGeneratedProperties, +} from '../../utils'; +import { getListResponseMockWithoutAutoGeneratedValues } from '../../../../plugins/lists/common/schemas/response/list_schema.mock'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + + describe('delete_lists', () => { + describe('deleting lists', () => { + beforeEach(async () => { + await createListsIndex(supertest); + }); + + afterEach(async () => { + await deleteListsIndex(supertest); + }); + + it('should delete a single list with a list id', async () => { + // create a list + await supertest + .post(LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListSchemaMock()) + .expect(200); + + // delete the list by its list id + const { body } = await supertest + .delete(`${LIST_URL}?id=${getCreateMinimalListSchemaMock().id}`) + .set('kbn-xsrf', 'true') + .expect(200); + + const bodyToCompare = removeListServerGeneratedProperties(body); + expect(bodyToCompare).to.eql(getListResponseMockWithoutAutoGeneratedValues()); + }); + + it('should delete a single list using an auto generated id', async () => { + // add a list + const { body: bodyWithCreatedList } = await supertest + .post(LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListSchemaMock()) + .expect(200); + + // delete that list by its auto-generated id + const { body } = await supertest + .delete(`${LIST_URL}?id=${bodyWithCreatedList.id}`) + .set('kbn-xsrf', 'true') + .expect(200); + + const bodyToCompare = removeListServerGeneratedProperties(body); + expect(bodyToCompare).to.eql(getListResponseMockWithoutAutoGeneratedValues()); + }); + + it('should return an error if the id does not exist when trying to delete it', async () => { + const { body } = await supertest + .delete(`${LIST_URL}?id=c1e1b359-7ac1-4e96-bc81-c683c092436f`) + .set('kbn-xsrf', 'true') + .expect(404); + + expect(body).to.eql({ + message: 'list id: "c1e1b359-7ac1-4e96-bc81-c683c092436f" was not found', + status_code: 404, + }); + }); + }); + }); +}; diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/export_list_items.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/export_list_items.ts new file mode 100644 index 0000000000000..6fe783fc497f2 --- /dev/null +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/export_list_items.ts @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { getCreateMinimalListItemSchemaMock } from '../../../../plugins/lists/common/schemas/request/create_list_item_schema.mock'; +import { getCreateMinimalListSchemaMock } from '../../../../plugins/lists/common/schemas/request/create_list_schema.mock'; +import { LIST_ID, NAME } from '../../../../plugins/lists/common/constants.mock'; +import { CreateListItemSchema } from '../../../../plugins/lists/common/schemas'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +import { LIST_ITEM_URL, LIST_URL } from '../../../../plugins/lists/common/constants'; + +import { createListsIndex, deleteListsIndex, binaryToString } from '../../utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + + describe('export_list_items', () => { + describe('exporting lists', () => { + beforeEach(async () => { + await createListsIndex(supertest); + }); + + afterEach(async () => { + await deleteListsIndex(supertest); + }); + + it('should set the response content types to be expected', async () => { + await supertest + .post(LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListSchemaMock()) + .expect(200); + + await supertest + .post(LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListItemSchemaMock()) + .expect(200); + + await supertest + .post(`${LIST_ITEM_URL}/_export?list_id=${LIST_ID}`) + .set('kbn-xsrf', 'true') + .expect('Content-Disposition', `attachment; filename="${NAME}"`) + .expect(200); + }); + + it('should export a single list item with a list id', async () => { + await supertest + .post(LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListSchemaMock()) + .expect(200); + + await supertest + .post(LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListItemSchemaMock()) + .expect(200); + + const { body } = await supertest + .post(`${LIST_ITEM_URL}/_export?list_id=${LIST_ID}`) + .set('kbn-xsrf', 'true') + .expect(200) + .parse(binaryToString); + + expect(body.toString()).to.eql('127.0.0.1\n'); + }); + + it('should export two list items with a list id', async () => { + await supertest + .post(LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListSchemaMock()) + .expect(200); + + await supertest + .post(LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListItemSchemaMock()) + .expect(200); + + const secondList: CreateListItemSchema = { + ...getCreateMinimalListItemSchemaMock(), + id: 'list-item-2', + value: '127.0.0.2', + }; + await supertest.post(LIST_ITEM_URL).set('kbn-xsrf', 'true').send(secondList).expect(200); + + const { body } = await supertest + .post(`${LIST_ITEM_URL}/_export?list_id=${LIST_ID}`) + .set('kbn-xsrf', 'true') + .expect(200) + .parse(binaryToString); + + expect(body.toString()).to.eql('127.0.0.2\n127.0.0.1\n'); + }); + }); + }); +}; diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/find_list_items.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/find_list_items.ts new file mode 100644 index 0000000000000..4c1f3dfdb6703 --- /dev/null +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/find_list_items.ts @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +import { LIST_ITEM_ID, LIST_ID } from '../../../../plugins/lists/common/constants.mock'; +import { getListItemResponseMockWithoutAutoGeneratedValues } from '../../../../plugins/lists/common/schemas/response/list_item_schema.mock'; +import { getCreateMinimalListItemSchemaMock } from '../../../../plugins/lists/common/schemas/request/create_list_item_schema.mock'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { LIST_URL, LIST_ITEM_URL } from '../../../../plugins/lists/common/constants'; + +import { getCreateMinimalListSchemaMock } from '../../../../plugins/lists/common/schemas/request/create_list_schema.mock'; +import { + createListsIndex, + deleteListsIndex, + removeListItemServerGeneratedProperties, +} from '../../utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + + describe('find_list_items', () => { + describe('find list items', () => { + beforeEach(async () => { + await createListsIndex(supertest); + }); + + afterEach(async () => { + await deleteListsIndex(supertest); + }); + + it('should give a validation error if the list_id is not supplied', async () => { + const { body } = await supertest + .get(`${LIST_ITEM_URL}/_find`) + .set('kbn-xsrf', 'true') + .send() + .expect(400); + + expect(body).to.eql({ + error: 'Bad Request', + message: '[request query]: Invalid value "undefined" supplied to "list_id"', + statusCode: 400, + }); + }); + + it('should give a 404 if the list has not been created yet', async () => { + const { body } = await supertest + .get(`${LIST_ITEM_URL}/_find?list_id=${LIST_ITEM_ID}`) + .set('kbn-xsrf', 'true') + .send() + .expect(404); + + expect(body).to.eql({ + message: 'list id: "some-list-item-id" does not exist', + status_code: 404, + }); + }); + + it('should return an empty find body correctly if no list items are loaded', async () => { + await supertest + .post(LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListSchemaMock()) + .expect(200); + + const { body } = await supertest + .get(`${LIST_ITEM_URL}/_find?list_id=${LIST_ID}`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(body).to.eql({ + cursor: 'WzBd', + data: [], + page: 1, + per_page: 20, + total: 0, + }); + }); + + it('should return a single list item when a single list item is loaded from a find with defaults added', async () => { + await supertest + .post(LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListSchemaMock()) + .expect(200); + + await supertest + .post(LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListItemSchemaMock()) + .expect(200); + + const { body } = await supertest + .get(`${LIST_ITEM_URL}/_find?list_id=${LIST_ID}`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + body.data = [removeListItemServerGeneratedProperties(body.data[0])]; + // cursor is a constant changing value so we have to delete it as well. + delete body.cursor; + expect(body).to.eql({ + data: [getListItemResponseMockWithoutAutoGeneratedValues()], + page: 1, + per_page: 20, + total: 1, + }); + }); + }); + }); +}; diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/find_lists.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/find_lists.ts new file mode 100644 index 0000000000000..7efe28a0b01a1 --- /dev/null +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/find_lists.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { LIST_URL } from '../../../../plugins/lists/common/constants'; + +import { getCreateMinimalListSchemaMock } from '../../../../plugins/lists/common/schemas/request/create_list_schema.mock'; +import { + createListsIndex, + deleteListsIndex, + removeListServerGeneratedProperties, +} from '../../utils'; +import { getListResponseMockWithoutAutoGeneratedValues } from '../../../../plugins/lists/common/schemas/response/list_schema.mock'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + + describe('find_lists', () => { + describe('find lists', () => { + beforeEach(async () => { + await createListsIndex(supertest); + }); + + afterEach(async () => { + await deleteListsIndex(supertest); + }); + + it('should return an empty find body correctly if no lists are loaded', async () => { + const { body } = await supertest + .get(`${LIST_URL}/_find`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(body).to.eql({ + cursor: 'WzBd', + data: [], + page: 1, + per_page: 20, + total: 0, + }); + }); + + it('should return a single list when a single list is loaded from a find with defaults added', async () => { + // add a single list + await supertest + .post(LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListSchemaMock()) + .expect(200); + + // query the single list from _find + const { body } = await supertest + .get(`${LIST_URL}/_find`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + body.data = [removeListServerGeneratedProperties(body.data[0])]; + // cursor is a constant changing value so we have to delete it as well. + delete body.cursor; + expect(body).to.eql({ + data: [getListResponseMockWithoutAutoGeneratedValues()], + page: 1, + per_page: 20, + total: 1, + }); + }); + }); + }); +}; diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/import_list_items.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/import_list_items.ts new file mode 100644 index 0000000000000..4befb6bbaf050 --- /dev/null +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/import_list_items.ts @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { ListItemSchema } from '../../../../plugins/lists/common/schemas'; +import { getListItemResponseMockWithoutAutoGeneratedValues } from '../../../../plugins/lists/common/schemas/response/list_item_schema.mock'; +import { ListSchema } from '../../../../plugins/lists/common'; +import { getListResponseMockWithoutAutoGeneratedValues } from '../../../../plugins/lists/common/schemas/response/list_schema.mock'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +import { LIST_ITEM_URL } from '../../../../plugins/lists/common/constants'; + +import { + createListsIndex, + deleteListsIndex, + removeListServerGeneratedProperties, + removeListItemServerGeneratedProperties, + waitFor, +} from '../../utils'; + +import { getImportListItemAsBuffer } from '../../../../plugins/lists/common/schemas/request/import_list_item_schema.mock'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + + describe('import_list_items', () => { + describe('importing list items without an index', () => { + it('should not import a list item if the index does not exist yet', async () => { + const { body } = await supertest + .post(`${LIST_ITEM_URL}/_import?type=ip`) + .set('kbn-xsrf', 'true') + .attach('file', getImportListItemAsBuffer(['127.0.0.1', '127.0.0.2']), 'list_items.txt') + .expect('Content-Type', 'application/json; charset=utf-8') + .expect(400); + + expect(body).to.eql({ + status_code: 400, + message: + 'To import a list item, the index must exist first. Index ".lists-default" does not exist', + }); + }); + }); + + describe('importing rules with an index', () => { + beforeEach(async () => { + await createListsIndex(supertest); + }); + + afterEach(async () => { + await deleteListsIndex(supertest); + }); + + it('should set the response content types to be expected when importing two items', async () => { + await supertest + .post(`${LIST_ITEM_URL}/_import?type=ip`) + .set('kbn-xsrf', 'true') + .attach('file', getImportListItemAsBuffer(['127.0.0.1', '127.0.0.2']), 'list_items.txt') + .expect('Content-Type', 'application/json; charset=utf-8') + .expect(200); + }); + + it('should report that it imported a simple list successfully', async () => { + const { body } = await supertest + .post(`${LIST_ITEM_URL}/_import?type=ip`) + .set('kbn-xsrf', 'true') + .attach('file', getImportListItemAsBuffer(['127.0.0.1', '127.0.0.2']), 'list_items.txt') + .expect('Content-Type', 'application/json; charset=utf-8') + .expect(200); + + const bodyToCompare = removeListServerGeneratedProperties(body); + const outputtedList: Partial = { + ...getListResponseMockWithoutAutoGeneratedValues(), + name: 'list_items.txt', + description: 'File uploaded from file system of list_items.txt', + }; + expect(bodyToCompare).to.eql(outputtedList); + }); + + it('should be able to read imported list items back out correctly', async () => { + await supertest + .post(`${LIST_ITEM_URL}/_import?type=ip`) + .set('kbn-xsrf', 'true') + .attach('file', getImportListItemAsBuffer(['127.0.0.1', '127.0.0.2']), 'list_items.txt') + .expect(200); + + // Although we try to be aggressive with waitFor in the lists code base, there is still not guarantees + // that we will have the data just yet so we have to do a waitFor here for when it shows up + await waitFor(async () => { + const { status } = await supertest + .get(`${LIST_ITEM_URL}?list_id=list_items.txt&value=127.0.0.1`) + .send(); + return status !== 404; + }); + const { body } = await supertest + .get(`${LIST_ITEM_URL}?list_id=list_items.txt&value=127.0.0.1`) + .send() + .expect(200); + + const bodyToCompare = removeListItemServerGeneratedProperties(body[0]); + const outputtedList: Partial = { + ...getListItemResponseMockWithoutAutoGeneratedValues(), + list_id: 'list_items.txt', + }; + expect(bodyToCompare).to.eql(outputtedList); + }); + }); + }); +}; diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/index.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/index.ts new file mode 100644 index 0000000000000..302877a680aa6 --- /dev/null +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/index.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default ({ loadTestFile }: FtrProviderContext): void => { + describe('lists api security and spaces enabled', function () { + this.tags('ciGroup1'); + + loadTestFile(require.resolve('./create_lists')); + loadTestFile(require.resolve('./create_list_items')); + loadTestFile(require.resolve('./read_lists')); + loadTestFile(require.resolve('./read_list_items')); + loadTestFile(require.resolve('./update_lists')); + loadTestFile(require.resolve('./update_list_items')); + loadTestFile(require.resolve('./delete_lists')); + loadTestFile(require.resolve('./delete_list_items')); + loadTestFile(require.resolve('./find_lists')); + loadTestFile(require.resolve('./find_list_items')); + loadTestFile(require.resolve('./import_list_items')); + loadTestFile(require.resolve('./export_list_items')); + }); +}; diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/read_list_items.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/read_list_items.ts new file mode 100644 index 0000000000000..5469973d70e50 --- /dev/null +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/read_list_items.ts @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +import { getListItemResponseMockWithoutAutoGeneratedValues } from '../../../../plugins/lists/common/schemas/response/list_item_schema.mock'; +import { getCreateMinimalListItemSchemaMock } from '../../../../plugins/lists/common/schemas/request/create_list_item_schema.mock'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { LIST_URL, LIST_ITEM_URL } from '../../../../plugins/lists/common/constants'; + +import { getCreateMinimalListSchemaMock } from '../../../../plugins/lists/common/schemas/request/create_list_schema.mock'; +import { + createListsIndex, + deleteListsIndex, + removeListItemServerGeneratedProperties, +} from '../../utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + + describe('read_list_items', () => { + describe('reading list items', () => { + beforeEach(async () => { + await createListsIndex(supertest); + }); + + afterEach(async () => { + await deleteListsIndex(supertest); + }); + + it('should be able to read a single list item using id', async () => { + await supertest + .post(LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListSchemaMock()) + .expect(200); + + await supertest + .post(LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListItemSchemaMock()) + .expect(200); + + const { body } = await supertest + .get(`${LIST_ITEM_URL}?id=${getCreateMinimalListItemSchemaMock().id}`) + .set('kbn-xsrf', 'true') + .expect(200); + + const bodyToCompare = removeListItemServerGeneratedProperties(body); + expect(bodyToCompare).to.eql(getListItemResponseMockWithoutAutoGeneratedValues()); + }); + + it('should be able to read a single list item with an auto-generated list id', async () => { + await supertest + .post(LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListSchemaMock()) + .expect(200); + + const { body: createListBody } = await supertest + .post(LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListItemSchemaMock()) + .expect(200); + + const { body } = await supertest + .get(`${LIST_ITEM_URL}?id=${createListBody.id}`) + .set('kbn-xsrf', 'true') + .expect(200); + + const bodyToCompare = removeListItemServerGeneratedProperties(body); + expect(bodyToCompare).to.eql(getListItemResponseMockWithoutAutoGeneratedValues()); + }); + + it('should return 404 if given a fake id', async () => { + await supertest + .post(LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListSchemaMock()) + .expect(200); + + const { body } = await supertest + .get(`${LIST_ITEM_URL}?id=c1e1b359-7ac1-4e96-bc81-c683c092436f`) + .set('kbn-xsrf', 'true') + .expect(404); + + expect(body).to.eql({ + status_code: 404, + message: 'list item id: "c1e1b359-7ac1-4e96-bc81-c683c092436f" does not exist', + }); + }); + }); + }); +}; diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/read_lists.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/read_lists.ts new file mode 100644 index 0000000000000..c3c8cb39d023b --- /dev/null +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/read_lists.ts @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { LIST_URL } from '../../../../plugins/lists/common/constants'; + +import { + getCreateMinimalListSchemaMock, + getCreateMinimalListSchemaMockWithoutId, +} from '../../../../plugins/lists/common/schemas/request/create_list_schema.mock'; +import { + createListsIndex, + deleteListsIndex, + removeListServerGeneratedProperties, +} from '../../utils'; +import { getListResponseMockWithoutAutoGeneratedValues } from '../../../../plugins/lists/common/schemas/response/list_schema.mock'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + + describe('read_lists', () => { + describe('reading lists', () => { + beforeEach(async () => { + await createListsIndex(supertest); + }); + + afterEach(async () => { + await deleteListsIndex(supertest); + }); + + it('should be able to read a single list using id', async () => { + // create a simple list to read + await supertest + .post(LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListSchemaMock()) + .expect(200); + + const { body } = await supertest + .get(`${LIST_URL}?id=${getCreateMinimalListSchemaMock().id}`) + .set('kbn-xsrf', 'true') + .expect(200); + + const bodyToCompare = removeListServerGeneratedProperties(body); + expect(bodyToCompare).to.eql(getListResponseMockWithoutAutoGeneratedValues()); + }); + + it('should be able to read a single list with an auto-generated list id', async () => { + // create a simple list to read + const { body: createListBody } = await supertest + .post(LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListSchemaMockWithoutId()) + .expect(200); + + const { body } = await supertest + .get(`${LIST_URL}?id=${createListBody.id}`) + .set('kbn-xsrf', 'true') + .expect(200); + + const bodyToCompare = removeListServerGeneratedProperties(body); + expect(bodyToCompare).to.eql(getListResponseMockWithoutAutoGeneratedValues()); + }); + + it('should return 404 if given a fake id', async () => { + const { body } = await supertest + .get(`${LIST_URL}?id=c1e1b359-7ac1-4e96-bc81-c683c092436f`) + .set('kbn-xsrf', 'true') + .expect(404); + + expect(body).to.eql({ + status_code: 404, + message: 'list id: "c1e1b359-7ac1-4e96-bc81-c683c092436f" does not exist', + }); + }); + }); + }); +}; diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/update_list_items.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/update_list_items.ts new file mode 100644 index 0000000000000..3ff496216eb71 --- /dev/null +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/update_list_items.ts @@ -0,0 +1,154 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +import { getListItemResponseMockWithoutAutoGeneratedValues } from '../../../../plugins/lists/common/schemas/response/list_item_schema.mock'; +import { getCreateMinimalListItemSchemaMock } from '../../../../plugins/lists/common/schemas/request/create_list_item_schema.mock'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { LIST_URL, LIST_ITEM_URL } from '../../../../plugins/lists/common/constants'; + +import { getCreateMinimalListSchemaMock } from '../../../../plugins/lists/common/schemas/request/create_list_schema.mock'; +import { + createListsIndex, + deleteListsIndex, + removeListItemServerGeneratedProperties, +} from '../../utils'; +import { getUpdateMinimalListItemSchemaMock } from '../../../../plugins/lists/common/schemas/request/update_list_item_schema.mock'; +import { + UpdateListItemSchema, + CreateListItemSchema, + ListItemSchema, +} from '../../../../plugins/lists/common/schemas'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + + describe('update_list_items', () => { + describe('update list items', () => { + beforeEach(async () => { + await createListsIndex(supertest); + }); + + afterEach(async () => { + await deleteListsIndex(supertest); + }); + + it('should update a single list item property of value using an id', async () => { + // create a simple list + await supertest + .post(LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListSchemaMock()) + .expect(200); + + // create a simple list item + await supertest + .post(LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListItemSchemaMock()) + .expect(200); + + // update a simple list item's value + const updatedListItem: UpdateListItemSchema = { + ...getUpdateMinimalListItemSchemaMock(), + value: '192.168.0.2', + }; + + const { body } = await supertest + .put(LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(updatedListItem) + .expect(200); + + const outputListItem: Partial = { + ...getListItemResponseMockWithoutAutoGeneratedValues(), + value: '192.168.0.2', + }; + const bodyToCompare = removeListItemServerGeneratedProperties(body); + expect(bodyToCompare).to.eql(outputListItem); + }); + + it('should update a single list item of value using an auto-generated id of both list and list item', async () => { + const { id, ...listNoId } = getCreateMinimalListSchemaMock(); + // create a simple list with no id which will use an auto-generated id + const { body: createListBody } = await supertest + .post(LIST_URL) + .set('kbn-xsrf', 'true') + .send(listNoId) + .expect(200); + + // create a simple list item also with an auto-generated id using the list's auto-generated id + const listItem: CreateListItemSchema = { + ...getCreateMinimalListItemSchemaMock(), + list_id: createListBody.id, + }; + const { body: createListItemBody } = await supertest + .post(LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(listItem) + .expect(200); + + // update a simple list item's value + const updatedList: UpdateListItemSchema = { + ...getUpdateMinimalListItemSchemaMock(), + id: createListItemBody.id, + value: '192.168.0.2', + }; + const { body } = await supertest + .put(LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(updatedList) + .expect(200); + + const outputListItem: Partial = { + ...getListItemResponseMockWithoutAutoGeneratedValues(), + value: '192.168.0.2', + }; + const bodyToCompare = { + ...removeListItemServerGeneratedProperties(body), + list_id: outputListItem.list_id, + }; + expect(bodyToCompare).to.eql(outputListItem); + }); + + it('should give a 404 if it is given a fake id', async () => { + // create a simple list + await supertest + .post(LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListSchemaMock()) + .expect(200); + + // create a simple list item + await supertest + .post(LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListItemSchemaMock()) + .expect(200); + + // update a simple list item's value + const updatedListItem: UpdateListItemSchema = { + ...getUpdateMinimalListItemSchemaMock(), + id: 'some-other-id', + value: '192.168.0.2', + }; + + const { body } = await supertest + .put(LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(updatedListItem) + .expect(404); + + expect(body).to.eql({ + status_code: 404, + message: 'list item id: "some-other-id" not found', + }); + }); + }); + }); +}; diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/update_lists.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/update_lists.ts new file mode 100644 index 0000000000000..04d77ffe21370 --- /dev/null +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/update_lists.ts @@ -0,0 +1,141 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { LIST_URL } from '../../../../plugins/lists/common/constants'; + +import { getCreateMinimalListSchemaMock } from '../../../../plugins/lists/common/schemas/request/create_list_schema.mock'; +import { + createListsIndex, + deleteListsIndex, + removeListServerGeneratedProperties, +} from '../../utils'; +import { getListResponseMockWithoutAutoGeneratedValues } from '../../../../plugins/lists/common/schemas/response/list_schema.mock'; +import { getUpdateMinimalListSchemaMock } from '../../../../plugins/lists/common/schemas/request/update_list_schema.mock'; +import { UpdateListSchema, ListSchema } from '../../../../plugins/lists/common/schemas'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + + describe('update_lists', () => { + describe('update lists', () => { + beforeEach(async () => { + await createListsIndex(supertest); + }); + + afterEach(async () => { + await deleteListsIndex(supertest); + }); + + it('should update a single list property of name using an id', async () => { + // create a simple list + await supertest + .post(LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListSchemaMock()) + .expect(200); + + // update a simple list's name + const updatedList: UpdateListSchema = { + ...getUpdateMinimalListSchemaMock(), + name: 'some other name', + }; + + const { body } = await supertest + .put(LIST_URL) + .set('kbn-xsrf', 'true') + .send(updatedList) + .expect(200); + + const outputList: Partial = { + ...getListResponseMockWithoutAutoGeneratedValues(), + name: 'some other name', + version: 2, + }; + const bodyToCompare = removeListServerGeneratedProperties(body); + expect(bodyToCompare).to.eql(outputList); + }); + + it('should update a single list property of name using an auto-generated id', async () => { + const { id, ...listNoId } = getCreateMinimalListSchemaMock(); + // create a simple list with no id which will use an auto-generated id + const { body: createListBody } = await supertest + .post(LIST_URL) + .set('kbn-xsrf', 'true') + .send(listNoId) + .expect(200); + + // update a simple list's name + const updatedList: UpdateListSchema = { + ...getUpdateMinimalListSchemaMock(), + id: createListBody.id, + name: 'some other name', + }; + const { body } = await supertest + .put(LIST_URL) + .set('kbn-xsrf', 'true') + .send(updatedList) + .expect(200); + + const outputList: Partial = { + ...getListResponseMockWithoutAutoGeneratedValues(), + name: 'some other name', + version: 2, + }; + const bodyToCompare = removeListServerGeneratedProperties(body); + expect(bodyToCompare).to.eql(outputList); + }); + + it('should change the version of a list when it updates a property', async () => { + // create a simple list + await supertest + .post(LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateMinimalListSchemaMock()) + .expect(200); + + // update a simple list property of name and description + const updatedList: UpdateListSchema = { + ...getUpdateMinimalListSchemaMock(), + name: 'some other name', + description: 'some other description', + }; + + const { body } = await supertest.put(LIST_URL).set('kbn-xsrf', 'true').send(updatedList); + + const outputList: Partial = { + ...getListResponseMockWithoutAutoGeneratedValues(), + name: 'some other name', + description: 'some other description', + version: 2, + }; + + const bodyToCompare = removeListServerGeneratedProperties(body); + expect(bodyToCompare).to.eql(outputList); + }); + + it('should give a 404 if it is given a fake id', async () => { + const simpleList: UpdateListSchema = { + ...getUpdateMinimalListSchemaMock(), + id: '5096dec6-b6b9-4d8d-8f93-6c2602079d9d', + }; + const { body } = await supertest + .put(LIST_URL) + .set('kbn-xsrf', 'true') + .send(simpleList) + .expect(404); + + expect(body).to.eql({ + status_code: 404, + message: 'list id: "5096dec6-b6b9-4d8d-8f93-6c2602079d9d" not found', + }); + }); + }); + }); +}; diff --git a/x-pack/test/lists_api_integration/utils.ts b/x-pack/test/lists_api_integration/utils.ts new file mode 100644 index 0000000000000..272768fdf50b3 --- /dev/null +++ b/x-pack/test/lists_api_integration/utils.ts @@ -0,0 +1,126 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SuperTest } from 'supertest'; +import supertestAsPromised from 'supertest-as-promised'; + +import { ListItemSchema } from '../../plugins/lists/common/schemas'; +import { ListSchema } from '../../plugins/lists/common'; +import { LIST_INDEX } from '../../plugins/lists/common/constants'; + +/** + * Creates the lists and lists items index for use inside of beforeEach blocks of tests + * This will retry 20 times before giving up and hopefully still not interfere with other tests + * @param supertest The supertest client library + */ +export const createListsIndex = async ( + supertest: SuperTest, + retryCount = 20 +): Promise => { + if (retryCount > 0) { + try { + await supertest.post(LIST_INDEX).set('kbn-xsrf', 'true').send(); + } catch (err) { + // eslint-disable-next-line no-console + console.log( + `Failure trying to create the lists index, retries left are: ${retryCount - 1}`, + err + ); + await createListsIndex(supertest, retryCount - 1); + } + } else { + // eslint-disable-next-line no-console + console.log('Could not createListsIndex, no retries are left'); + } +}; + +/** + * Deletes the lists index for use inside of afterEach blocks of tests + * @param supertest The supertest client library + */ +export const deleteListsIndex = async ( + supertest: SuperTest, + retryCount = 20 +): Promise => { + if (retryCount > 0) { + try { + await supertest.delete(LIST_INDEX).set('kbn-xsrf', 'true').send(); + } catch (err) { + // eslint-disable-next-line no-console + console.log(`Failure trying to deleteListsIndex, retries left are: ${retryCount - 1}`, err); + await deleteListsIndex(supertest, retryCount - 1); + } + } else { + // eslint-disable-next-line no-console + console.log('Could not deleteListsIndex, no retries are left'); + } +}; + +/** + * This will remove server generated properties such as date times, etc... + * @param list List to pass in to remove typical server generated properties + */ +export const removeListServerGeneratedProperties = ( + list: Partial +): Partial => { + /* eslint-disable-next-line @typescript-eslint/naming-convention */ + const { created_at, updated_at, id, tie_breaker_id, _version, ...removedProperties } = list; + return removedProperties; +}; + +/** + * This will remove server generated properties such as date times, etc... + * @param list List to pass in to remove typical server generated properties + */ +export const removeListItemServerGeneratedProperties = ( + list: Partial +): Partial => { + /* eslint-disable-next-line @typescript-eslint/naming-convention */ + const { created_at, updated_at, id, tie_breaker_id, _version, ...removedProperties } = list; + return removedProperties; +}; + +// Similar to ReactJs's waitFor from here: https://testing-library.com/docs/dom-testing-library/api-async#waitfor +export const waitFor = async ( + functionToTest: () => Promise, + maxTimeout: number = 5000, + timeoutWait: number = 10 +) => { + await new Promise(async (resolve, reject) => { + let found = false; + let numberOfTries = 0; + while (!found && numberOfTries < Math.floor(maxTimeout / timeoutWait)) { + const itPasses = await functionToTest(); + if (itPasses) { + found = true; + } else { + numberOfTries++; + } + await new Promise((resolveTimeout) => setTimeout(resolveTimeout, timeoutWait)); + } + if (found) { + resolve(); + } else { + reject(new Error('timed out waiting for function condition to be true')); + } + }); +}; + +/** + * Useful for export_api testing to convert from a multi-part binary back to a string + * @param res Response + * @param callback Callback + */ +export const binaryToString = (res: any, callback: any): void => { + res.setEncoding('binary'); + res.data = ''; + res.on('data', (chunk: any) => { + res.data += chunk; + }); + res.on('end', () => { + callback(null, Buffer.from(res.data)); + }); +};