From 5f4c211ea37d61e7e7ae54e04d087bdf4fd5f557 Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Fri, 20 Nov 2020 12:09:38 -0700 Subject: [PATCH] [Security Solutions][Detection Engine] Adds e2e FTR runtime support and 213 tests for exception lists (#83764) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Adds support to the end to end (e2e) functional test runner (FTR) support for rule runtime tests as well as 213 tests for the exception lists which include value based lists. Previously we had limited runtime support, but as I scaled up runtime tests from 5 to 200+ I noticed in a lot of areas we had to use improved techniques for determinism. The runtime support being added is our next step of tests. Up to now most of our e2e FTR tests have been structural testing of REST and API integration tests. Basically up to now 95% tests are API structural as: * Call REST input related to a rule such as GET/PUT/POST/PATCH/DELETE. * Check REST output of the rule, did it match expected output body and status code? * In some rare cases we check if the the rule can be executed and we get a status of 'succeeded' With only a small part of our tests ~5%, `generating_signals.ts` was checking the signals being produced. However, we cannot have confidence in runtime based tests until the structural tests have been built up and run through the weeks against PR's to ensure that those are stable and deterministic. Now that we have confidence and 90%+ coverage of the structural REST based tests, we are building up newer sets of tests which allow us to do runtime based validation tests to increase confidence that: * Detection engine produces signals as expected * Structure of the signals are as expected, including signal on signals * Exceptions to signals are working as expected * Most runtime bugs can be TDD'ed with e2e FTR's and regressions * Whack-a-mole will not happen * Consistency and predictability of signals is validated * Refactoring can occur with stronger confidence * Runtime tests are reference points for answering questions about existing bugs or adding new ones to test if users are experiencing unexpected behaviors * Scaling tests can happen without failures * Velocity for creating tests increases as the utilities and examples increase Lastly, this puts us within striking distance of creating FTR's for different common class of runtime situations such as: * Creating tests that exercise each rule against a set of data criteria and get signal hits * Creating tests that validate the rule overrides operate as expected against data sets * Creating tests that validate malfunctions, corner cases, or misuse cases such as data sets that are _all_ arrays or data sets that put numbers as strings or throws in an expected `null` instead of a value. These tests follow the pattern of: * Add the smallest data set to a folder in data.json (not gzip format) * Add the smallest mapping to that folder (mapping.json) * Call REST input related to exception lists, value lists, adding prepackaged rules, etc... * Call REST input related endpoint with utilities to create and activate the rule * Wait for the rule to go into the `succeeded` phase * Wait for the N exact signals specific to that rule to be available * Check against the set of signals to ensure that the matches are exactly as expected Example of one runtime test: A keyword data set is added to a folder called "keyword" but you can add one anywhere you want under `es_archives`, I just grouped mine depending on the situation of the runtime. Small non-gzipped tests `data.json` and `mappings.json` are the best approach for small focused tests. For _larger_ tests and cases I would and sometimes do use things such as auditbeat but try to avoid using larger data sets in favor of smaller focused test cases to validate the runtime is operating as expected. ```ts { "type": "doc", "value": { "id": "1", "index": "long", "source": { "@timestamp": "2020-10-28T05:00:53.000Z", "long": 1 }, "type": "_doc" } } { "type": "doc", "value": { "id": "2", "index": "long", "source": { "@timestamp": "2020-10-28T05:01:53.000Z", "long": 2 }, "type": "_doc" } } { "type": "doc", "value": { "id": "3", "index": "long", "source": { "@timestamp": "2020-10-28T05:02:53.000Z", "long": 3 }, "type": "_doc" } } { "type": "doc", "value": { "id": "4", "index": "long", "source": { "@timestamp": "2020-10-28T05:03:53.000Z", "long": 4 }, "type": "_doc" } } ``` Mapping is added. Note that this is "ECS tolerant" but not necessarily all ECS meaning I can and will try to keep things simple where I can, but I have ensured that `"@timestamp"` is at least there. ```ts { "type": "index", "value": { "index": "long", "mappings": { "properties": { "@timestamp": { "type": "date" }, "long": { "type": "long" } } }, "settings": { "index": { "number_of_replicas": "1", "number_of_shards": "1" } } } } ``` Test is written with test utilities where the `beforeEach` and `afterEach` try and clean up the indexes and load/unload the archives to keep one test from effecting another. Note this is never going to be 100% possible so see below on how we add more determinism in case something escapes the sandbox. ```ts beforeEach(async () => { await createSignalsIndex(supertest); await createListsIndex(supertest); await esArchiver.load('rule_exceptions/keyword'); }); afterEach(async () => { await deleteSignalsIndex(supertest); await deleteAllAlerts(supertest); await deleteAllExceptions(es); await deleteListsIndex(supertest); await esArchiver.unload('rule_exceptions/keyword'); }); describe('"is" operator', () => { it('should filter 1 single keyword if it is set as an exception', async () => { const rule = getRuleForSignalTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, rule, [ [ { field: 'keyword', operator: 'included', type: 'match', value: 'word one', }, ], ]); await waitForRuleSuccess(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); expect(hits).to.eql(['word four', 'word three', 'word two']); }); }); ``` ### Changes for better determinism To support more determinism there are changes and utilities added which can be tuned during any sporadic failures we might encounter as well as better support unexpected changes to other Elastic Stack pieces such as alerting, task manager, etc... Get simple rule and others are now defaulting to false, meaning that the structural tests will no longer activate a rule and run it on task manger. This should cut down on error outputs as well as reduce stress and potentials for left over rules interfering with the runtime rules. ```ts export const getSimpleRule = (ruleId = 'rule-1', enabled = false): QueryCreateSchema => ({ ``` Not mandatory to use, but for most tests that should be runtime based tests, I use this function below which will enable it by default and run it using settings such as `type: 'query'`, `query: '*:*',` `from: '1900-01-01T00:00:00.000Z'`, to cut down on boiler plate noise. However, people can use whatever they want out of the grab bag or if their test is more readable to hand craft a REST request to create signals, or if they just want to call this and override where they want to, then 👍 . ```ts export const getRuleForSignalTesting = (index: string[], ruleId = 'rule-1', enabled = true) ``` This waits for a rule to succeed before continuing ```ts await waitForRuleSuccess(supertest, id); ``` I added a required array of id that _waits_ only for that particular id here. This is useful in case another test did not cleanup and you are getting signals being produced or left behind but need to wait specifically for yours. ```ts await waitForSignalsToBePresent(supertest, 4, [id]); ``` I only get the signals for a particular rule id using either the auto-generated id or the rule_id. It's safer to use the ones from the auto-generated id but either of these are fine if you're careful enough. ```ts const signalsOpen = await getSignalsById(supertest, id); const signalsOpen = await getSignalsByIds(supertest, [createdId]); const signalsOpen = await getSignalsByRuleIds(supertest, ['signal-on-signal']); ``` I delete all alerts now through a series of steps where it properly removes all rules using the rules bulk_delete and does it in such a way that all the API keys and alerting will be the best it can destroyed as well as double check that the alerts are showing up as being cleaned up before continuing. ```ts deleteAllAlerts() ``` When not explicitly testing something structural, prefer to use the utilities which can and will do retries in case there are over the wire failures or es failures. Examples are: ```ts installPrePackagedRules() waitForRuleSuccess() importFile() // This does a _lot_ of checks to ensure that the file is fully imported before continuing ``` Some of these utilities might still do a `expect(200);` but as we are and should use regular structural tests to cover those problems, these will probably be more and more removed when/if we hit test failures in favor of doing retries, waitFor, and countDowns. ### Checklist Delete any items that are not applicable to this PR. - [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 --- .../create_exception_list_schema.mock.ts | 10 + .../schemas/request/rule_schemas.mock.ts | 4 +- .../schemas/response/rules_schema.mocks.ts | 4 +- .../basic/tests/add_prepackaged_rules.ts | 121 +-- .../basic/tests/create_rules.ts | 3 +- .../basic/tests/create_rules_bulk.ts | 3 +- .../basic/tests/delete_rules.ts | 3 +- .../basic/tests/delete_rules_bulk.ts | 5 +- .../basic/tests/export_rules.ts | 3 +- .../basic/tests/find_rules.ts | 3 +- .../basic/tests/find_statuses.ts | 4 +- .../tests/get_prepackaged_rules_status.ts | 2 +- .../basic/tests/import_rules.ts | 19 +- .../tests/install_prepackaged_timelines.ts | 4 +- .../basic/tests/open_close_signals.ts | 46 +- .../basic/tests/patch_rules.ts | 3 +- .../basic/tests/patch_rules_bulk.ts | 3 +- .../basic/tests/read_rules.ts | 3 +- .../basic/tests/update_rules.ts | 3 +- .../basic/tests/update_rules_bulk.ts | 3 +- .../security_and_spaces/tests/add_actions.ts | 7 +- .../tests/add_prepackaged_rules.ts | 103 +-- .../tests/create_exceptions.ts | 17 +- .../security_and_spaces/tests/create_rules.ts | 8 +- .../tests/create_rules_bulk.ts | 8 +- .../tests/create_threat_matching.ts | 29 +- .../security_and_spaces/tests/delete_rules.ts | 3 +- .../tests/delete_rules_bulk.ts | 5 +- .../exception_operators_data_types/README.md | 21 + .../exception_operators_data_types/date.ts | 611 +++++++++++++ .../exception_operators_data_types/double.ts | 744 ++++++++++++++++ .../exception_operators_data_types/float.ts | 744 ++++++++++++++++ .../exception_operators_data_types/index.ts | 23 + .../exception_operators_data_types/integer.ts | 744 ++++++++++++++++ .../exception_operators_data_types/ip.ts | 622 +++++++++++++ .../exception_operators_data_types/keyword.ts | 555 ++++++++++++ .../exception_operators_data_types/long.ts | 744 ++++++++++++++++ .../exception_operators_data_types/text.ts | 827 ++++++++++++++++++ .../security_and_spaces/tests/export_rules.ts | 3 +- .../security_and_spaces/tests/find_rules.ts | 3 +- .../tests/find_statuses.ts | 4 +- .../tests/generating_signals.ts | 188 ++-- .../tests/get_prepackaged_rules_status.ts | 2 +- .../security_and_spaces/tests/import_rules.ts | 19 +- .../security_and_spaces/tests/index.ts | 1 + .../tests/open_close_signals.ts | 64 +- .../security_and_spaces/tests/patch_rules.ts | 3 +- .../tests/patch_rules_bulk.ts | 3 +- .../security_and_spaces/tests/read_rules.ts | 3 +- .../security_and_spaces/tests/update_rules.ts | 3 +- .../tests/update_rules_bulk.ts | 3 +- .../detection_engine_api_integration/utils.ts | 260 +++++- .../es_archives/rule_exceptions/README.md | 11 + .../rule_exceptions/date/data.json | 51 ++ .../rule_exceptions/date/mappings.json | 20 + .../rule_exceptions/double/data.json | 51 ++ .../rule_exceptions/double/mappings.json | 20 + .../double_as_string/data.json | 51 ++ .../double_as_string/mappings.json | 20 + .../rule_exceptions/float/data.json | 51 ++ .../rule_exceptions/float/mappings.json | 20 + .../rule_exceptions/float_as_string/data.json | 51 ++ .../float_as_string/mappings.json | 20 + .../rule_exceptions/integer/data.json | 51 ++ .../rule_exceptions/integer/mappings.json | 20 + .../integer_as_string/data.json | 51 ++ .../integer_as_string/mappings.json | 20 + .../es_archives/rule_exceptions/ip/data.json | 51 ++ .../rule_exceptions/ip/mappings.json | 20 + .../rule_exceptions/keyword/data.json | 51 ++ .../rule_exceptions/keyword/mappings.json | 20 + .../rule_exceptions/long/data.json | 51 ++ .../rule_exceptions/long/mappings.json | 20 + .../rule_exceptions/long_as_string/data.json | 51 ++ .../long_as_string/mappings.json | 20 + .../rule_exceptions/text/data.json | 51 ++ .../rule_exceptions/text/mappings.json | 20 + .../rule_exceptions/text_no_spaces/data.json | 51 ++ .../text_no_spaces/mappings.json | 20 + .../rule_exceptions/wildcard/data.json | 51 ++ .../rule_exceptions/wildcard/mappings.json | 20 + .../functional/es_archives/signals/README.md | 22 + .../tests/import_list_items.ts | 2 +- x-pack/test/lists_api_integration/utils.ts | 138 ++- 84 files changed, 7328 insertions(+), 462 deletions(-) create mode 100644 x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/README.md create mode 100644 x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/date.ts create mode 100644 x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/double.ts create mode 100644 x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/float.ts create mode 100644 x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/index.ts create mode 100644 x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/integer.ts create mode 100644 x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/ip.ts create mode 100644 x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/keyword.ts create mode 100644 x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/long.ts create mode 100644 x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/text.ts create mode 100644 x-pack/test/functional/es_archives/rule_exceptions/README.md create mode 100644 x-pack/test/functional/es_archives/rule_exceptions/date/data.json create mode 100644 x-pack/test/functional/es_archives/rule_exceptions/date/mappings.json create mode 100644 x-pack/test/functional/es_archives/rule_exceptions/double/data.json create mode 100644 x-pack/test/functional/es_archives/rule_exceptions/double/mappings.json create mode 100644 x-pack/test/functional/es_archives/rule_exceptions/double_as_string/data.json create mode 100644 x-pack/test/functional/es_archives/rule_exceptions/double_as_string/mappings.json create mode 100644 x-pack/test/functional/es_archives/rule_exceptions/float/data.json create mode 100644 x-pack/test/functional/es_archives/rule_exceptions/float/mappings.json create mode 100644 x-pack/test/functional/es_archives/rule_exceptions/float_as_string/data.json create mode 100644 x-pack/test/functional/es_archives/rule_exceptions/float_as_string/mappings.json create mode 100644 x-pack/test/functional/es_archives/rule_exceptions/integer/data.json create mode 100644 x-pack/test/functional/es_archives/rule_exceptions/integer/mappings.json create mode 100644 x-pack/test/functional/es_archives/rule_exceptions/integer_as_string/data.json create mode 100644 x-pack/test/functional/es_archives/rule_exceptions/integer_as_string/mappings.json create mode 100644 x-pack/test/functional/es_archives/rule_exceptions/ip/data.json create mode 100644 x-pack/test/functional/es_archives/rule_exceptions/ip/mappings.json create mode 100644 x-pack/test/functional/es_archives/rule_exceptions/keyword/data.json create mode 100644 x-pack/test/functional/es_archives/rule_exceptions/keyword/mappings.json create mode 100644 x-pack/test/functional/es_archives/rule_exceptions/long/data.json create mode 100644 x-pack/test/functional/es_archives/rule_exceptions/long/mappings.json create mode 100644 x-pack/test/functional/es_archives/rule_exceptions/long_as_string/data.json create mode 100644 x-pack/test/functional/es_archives/rule_exceptions/long_as_string/mappings.json create mode 100644 x-pack/test/functional/es_archives/rule_exceptions/text/data.json create mode 100644 x-pack/test/functional/es_archives/rule_exceptions/text/mappings.json create mode 100644 x-pack/test/functional/es_archives/rule_exceptions/text_no_spaces/data.json create mode 100644 x-pack/test/functional/es_archives/rule_exceptions/text_no_spaces/mappings.json create mode 100644 x-pack/test/functional/es_archives/rule_exceptions/wildcard/data.json create mode 100644 x-pack/test/functional/es_archives/rule_exceptions/wildcard/mappings.json create mode 100644 x-pack/test/functional/es_archives/signals/README.md diff --git a/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.mock.ts index 3150cb9975f21..ff39d91be7e4a 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.mock.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.mock.ts @@ -46,3 +46,13 @@ export const getCreateExceptionListMinimalSchemaMockWithoutId = (): CreateExcept name: NAME, type: ENDPOINT_TYPE, }); + +/** + * Useful for end to end testing with detections + */ +export const getCreateExceptionListDetectionSchemaMock = (): CreateExceptionListSchema => ({ + description: DESCRIPTION, + list_id: LIST_ID, + name: NAME, + type: 'detection', +}); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts index 6be51d2a1adc2..26d2a2cff2910 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts @@ -40,9 +40,11 @@ export const getCreateSavedQueryRulesSchemaMock = (ruleId = 'rule-1'): SavedQuer }); export const getCreateThreatMatchRulesSchemaMock = ( - ruleId = 'rule-1' + ruleId = 'rule-1', + enabled = false ): ThreatMatchCreateSchema => ({ description: 'Detecting root and admin users', + enabled, name: 'Query with a rule id', query: 'user.name: root or user.name: admin', severity: 'high', diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts index 08c544b9246e0..1bf6b64db2427 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts @@ -115,12 +115,12 @@ export const getThreatMatchingSchemaMock = (anchorDate: string = ANCHOR_DATE): R * Useful for e2e backend tests where it doesn't have date time and other * server side properties attached to it. */ -export const getThreatMatchingSchemaPartialMock = (): Partial => { +export const getThreatMatchingSchemaPartialMock = (enabled = false): Partial => { return { author: [], created_by: 'elastic', description: 'Detecting root and admin users', - enabled: true, + enabled, false_positives: [], from: 'now-6m', immutable: false, diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/add_prepackaged_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/add_prepackaged_rules.ts index c682c1f1f4640..b653d46905503 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/add_prepackaged_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/add_prepackaged_rules.ts @@ -5,6 +5,7 @@ */ import expect from '@kbn/expect'; +import { PrePackagedRulesAndTimelinesSchema } from '../../../../plugins/security_solution/common/detection_engine/schemas/response'; import { DETECTION_ENGINE_PREPACKAGED_URL } from '../../../../plugins/security_solution/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -13,6 +14,7 @@ import { deleteAllAlerts, deleteAllTimelines, deleteSignalsIndex, + installPrePackagedRules, waitFor, } from '../../utils'; @@ -45,18 +47,27 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); await deleteAllTimelines(es); }); - it('should contain rules_installed, rules_updated, timelines_installed, and timelines_updated', async () => { - const { body } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - expect(Object.keys(body)).to.eql([ + it('should create the prepackaged rules and return a count greater than zero, rules_updated to be zero, and contain the correct keys', async () => { + let responseBody: unknown; + await waitFor(async () => { + const { body, status } = await supertest + .put(DETECTION_ENGINE_PREPACKAGED_URL) + .set('kbn-xsrf', 'true') + .send(); + if (status === 200) { + responseBody = body; + } + return status === 200; + }, DETECTION_ENGINE_PREPACKAGED_URL); + + const prepackagedRules = responseBody as PrePackagedRulesAndTimelinesSchema; + expect(prepackagedRules.rules_installed).to.be.greaterThan(0); + expect(prepackagedRules.rules_updated).to.eql(0); + expect(Object.keys(prepackagedRules)).to.eql([ 'rules_installed', 'rules_updated', 'timelines_installed', @@ -64,52 +75,8 @@ export default ({ getService }: FtrProviderContext): void => { ]); }); - it('should create the prepackaged rules and return a count greater than zero', async () => { - const { body } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - expect(body.rules_installed).to.be.greaterThan(0); - }); - - it('should create the prepackaged timelines and return a count greater than zero', async () => { - const { body } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - expect(body.timelines_installed).to.be.greaterThan(0); - }); - - it('should create the prepackaged rules that the rules_updated is of size zero', async () => { - const { body } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - expect(body.rules_updated).to.eql(0); - }); - - it('should create the prepackaged timelines and the timelines_updated is of size zero', async () => { - const { body } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - expect(body.timelines_updated).to.eql(0); - }); - - it('should be possible to call the API twice and the second time the number of rules installed should be zero', async () => { - await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); + it('should be possible to call the API twice and the second time the number of rules installed should be zero as well as timeline', async () => { + await installPrePackagedRules(supertest); // NOTE: I call the GET call until eventually it becomes consistent and that the number of rules to install are zero. // This is to reduce flakiness where it can for a short period of time try to install the same rule twice. @@ -119,39 +86,23 @@ export default ({ getService }: FtrProviderContext): void => { .set('kbn-xsrf', 'true') .expect(200); return body.rules_not_installed === 0; - }); - - const { body } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - expect(body.rules_installed).to.eql(0); - }); - - it('should be possible to call the API twice and the second time the number of timelines installed should be zero', async () => { - await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); + }, `${DETECTION_ENGINE_PREPACKAGED_URL}/_status`); + let responseBody: unknown; await waitFor(async () => { - const { body } = await supertest - .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) + const { body, status } = await supertest + .put(DETECTION_ENGINE_PREPACKAGED_URL) .set('kbn-xsrf', 'true') - .expect(200); - return body.timelines_not_installed === 0; - }); - - const { body } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - expect(body.timelines_installed).to.eql(0); + .send(); + if (status === 200) { + responseBody = body; + } + return status === 200; + }, DETECTION_ENGINE_PREPACKAGED_URL); + + const prepackagedRules = responseBody as PrePackagedRulesAndTimelinesSchema; + expect(prepackagedRules.rules_installed).to.eql(0); + expect(prepackagedRules.timelines_installed).to.eql(0); }); }); }); diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts index 53a8f1f4ca5c0..a8a5f2abd072b 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts @@ -25,7 +25,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); - const es = getService('es'); describe('create_rules', () => { describe('validation errors', () => { @@ -51,7 +50,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should create a single rule with a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/create_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/basic/tests/create_rules_bulk.ts index 6c3b1c45e202e..73be4154db1eb 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/create_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/create_rules_bulk.ts @@ -23,7 +23,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); - const es = getService('es'); describe('create_rules_bulk', () => { describe('validation errors', () => { @@ -54,7 +53,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should create a single rule with a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules.ts index 7104e16f438c6..786e953843210 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules.ts @@ -24,7 +24,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); - const es = getService('es'); describe('delete_rules', () => { describe('deleting rules', () => { @@ -34,7 +33,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should delete a single rule with a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules_bulk.ts index 35b31d2ccfefa..66aa43e8a3817 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules_bulk.ts @@ -24,7 +24,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); - const es = getService('es'); describe('delete_rules_bulk', () => { describe('deleting rules bulk using DELETE', () => { @@ -34,7 +33,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should delete a single rule with a rule_id', async () => { @@ -146,7 +145,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should delete a single rule with a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/export_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/export_rules.ts index 2610796bdc384..4f76a0544a152 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/export_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/export_rules.ts @@ -22,7 +22,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); - const es = getService('es'); describe('export_rules', () => { describe('exporting rules', () => { @@ -32,7 +31,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should set the response content types to be expected', async () => { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/find_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/find_rules.ts index f496d035d8e60..2f06a84c7223b 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/find_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/find_rules.ts @@ -23,7 +23,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); - const es = getService('es'); describe('find_rules', () => { beforeEach(async () => { @@ -32,7 +31,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should return an empty find body correctly if no rules are loaded', async () => { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts b/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts index 9c20d58c5f4e5..fe80402b60731 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts @@ -30,7 +30,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); await deleteAllRulesStatuses(es); }); @@ -45,7 +45,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should return a single rule status when a single rule is loaded from a find status with defaults added', async () => { - const resBody = await createRule(supertest, getSimpleRule()); + const resBody = await createRule(supertest, getSimpleRule('rule-1', true)); await waitForRuleSuccess(supertest, resBody.id); diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/get_prepackaged_rules_status.ts b/x-pack/test/detection_engine_api_integration/basic/tests/get_prepackaged_rules_status.ts index 1bbfce42d2baa..c72b2e50b39fc 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/get_prepackaged_rules_status.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/get_prepackaged_rules_status.ts @@ -32,7 +32,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); await deleteAllTimelines(es); }); diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/import_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/import_rules.ts index c6294cfe6ec28..f5774e09bb5e9 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/import_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/import_rules.ts @@ -23,7 +23,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); - const es = getService('es'); describe('import_rules', () => { describe('importing rules without an index', () => { @@ -39,7 +38,7 @@ export default ({ getService }: FtrProviderContext): void => { .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=rule-1`) .send(); return body.status_code === 404; - }); + }, `${DETECTION_ENGINE_RULES_URL}?rule_id=rule-1`); // Try to fetch the rule which should still be a 404 (not found) const { body } = await supertest.get(`${DETECTION_ENGINE_RULES_URL}?rule_id=rule-1`).send(); @@ -86,7 +85,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should set the response content types to be expected', async () => { @@ -129,7 +128,7 @@ export default ({ getService }: FtrProviderContext): void => { await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') - .attach('file', getSimpleRuleAsNdjson(['rule-1'], true), 'rules.ndjson') + .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') .expect(200); const { body } = await supertest @@ -138,7 +137,7 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql(getSimpleRuleOutput('rule-1')); + expect(bodyToCompare).to.eql(getSimpleRuleOutput('rule-1', false)); }); it('should fail validation when importing a rule with malformed "from" params on the rules', async () => { @@ -330,7 +329,7 @@ export default ({ getService }: FtrProviderContext): void => { await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') - .attach('file', getSimpleRuleAsNdjson(['rule-1'], true), 'rules.ndjson') + .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') .expect(200); const simpleRule = getSimpleRule('rule-1'); @@ -422,17 +421,13 @@ export default ({ getService }: FtrProviderContext): void => { await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') - .attach('file', getSimpleRuleAsNdjson(['rule-1', 'rule-2'], true), 'rules.ndjson') + .attach('file', getSimpleRuleAsNdjson(['rule-1', 'rule-2']), 'rules.ndjson') .expect(200); await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') - .attach( - 'file', - getSimpleRuleAsNdjson(['rule-1', 'rule-2', 'rule-3'], true), - 'rules.ndjson' - ) + .attach('file', getSimpleRuleAsNdjson(['rule-1', 'rule-2', 'rule-3']), 'rules.ndjson') .expect(200); const { body: bodyOfRule1 } = await supertest diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/install_prepackaged_timelines.ts b/x-pack/test/detection_engine_api_integration/basic/tests/install_prepackaged_timelines.ts index 556217877968b..f70720cc752b2 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/install_prepackaged_timelines.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/install_prepackaged_timelines.ts @@ -29,7 +29,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); await deleteAllTimelines(es); }); @@ -72,7 +72,7 @@ export default ({ getService }: FtrProviderContext): void => { .set('kbn-xsrf', 'true') .expect(200); return body.timelines_not_installed === 0; - }); + }, `${TIMELINE_PREPACKAGED_URL}/_status`); const { body } = await supertest .put(TIMELINE_PREPACKAGED_URL) diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/open_close_signals.ts b/x-pack/test/detection_engine_api_integration/basic/tests/open_close_signals.ts index a84d9845085e0..f8a25b0081ef9 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/open_close_signals.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/open_close_signals.ts @@ -18,19 +18,19 @@ import { deleteSignalsIndex, setSignalStatus, getSignalStatusEmptyResponse, - getSimpleRule, getQuerySignalIds, deleteAllAlerts, createRule, waitForSignalsToBePresent, - getAllSignals, + getSignalsByIds, + waitForRuleSuccess, + getRuleForSignalTesting, } from '../../utils'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - const es = getService('es'); describe('open_close_signals', () => { describe('validation checks', () => { @@ -66,29 +66,31 @@ export default ({ getService }: FtrProviderContext) => { describe('tests with auditbeat data', () => { beforeEach(async () => { - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); await createSignalsIndex(supertest); await esArchiver.load('auditbeat/hosts'); }); afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); await esArchiver.unload('auditbeat/hosts'); }); it('should be able to execute and get 10 signals', async () => { - const rule = { ...getSimpleRule(), from: '1900-01-01T00:00:00.000Z', query: '*:*' }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 10); - const signalsOpen = await getAllSignals(supertest); + const rule = getRuleForSignalTesting(['auditbeat-*']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 10, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); expect(signalsOpen.hits.hits.length).equal(10); }); it('should be have set the signals in an open state initially', async () => { - const rule = { ...getSimpleRule(), from: '1900-01-01T00:00:00.000Z', query: '*:*' }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest); - const signalsOpen = await getAllSignals(supertest); + const rule = getRuleForSignalTesting(['auditbeat-*']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 10, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); const everySignalOpen = signalsOpen.hits.hits.every( ({ _source: { @@ -100,10 +102,11 @@ export default ({ getService }: FtrProviderContext) => { }); it('should be able to get a count of 10 closed signals when closing 10', async () => { - const rule = { ...getSimpleRule(), from: '1900-01-01T00:00:00.000Z', query: '*:*' }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 10); - const signalsOpen = await getAllSignals(supertest); + const rule = getRuleForSignalTesting(['auditbeat-*']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 10, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); // set all of the signals to the state of closed. There is no reason to use a waitUntil here @@ -126,10 +129,11 @@ export default ({ getService }: FtrProviderContext) => { }); it('should be able close 10 signals immediately and they all should be closed', async () => { - const rule = { ...getSimpleRule(), from: '1900-01-01T00:00:00.000Z', query: '*:*' }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest); - const signalsOpen = await getAllSignals(supertest); + const rule = getRuleForSignalTesting(['auditbeat-*']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 10, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); // set all of the signals to the state of closed. There is no reason to use a waitUntil here diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules.ts index 36a9649d875ca..28ea2e1ff8803 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules.ts @@ -23,7 +23,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); - const es = getService('es'); describe('patch_rules', () => { describe('patch rules', () => { @@ -33,7 +32,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should patch a single rule property of name using a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules_bulk.ts index 69330a2bf682a..e32771d0d917c 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules_bulk.ts @@ -23,7 +23,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); - const es = getService('es'); describe('patch_rules_bulk', () => { describe('patch rules bulk', () => { @@ -33,7 +32,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should patch a single rule property of name using a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/read_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/read_rules.ts index cfccb7436ea20..1697554441c16 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/read_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/read_rules.ts @@ -24,7 +24,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); - const es = getService('es'); describe('read_rules', () => { describe('reading rules', () => { @@ -34,7 +33,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should be able to read a single rule using rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/update_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/update_rules.ts index 2f5a043881eeb..d8e9c650c8116 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/update_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/update_rules.ts @@ -25,7 +25,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); - const es = getService('es'); describe('update_rules', () => { describe('update rules', () => { @@ -35,7 +34,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should update a single rule property of name using a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/update_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/basic/tests/update_rules_bulk.ts index 22aa40b0721a4..c5b65039aa116 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/update_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/update_rules_bulk.ts @@ -24,7 +24,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); - const es = getService('es'); describe('update_rules_bulk', () => { describe('update rules bulk', () => { @@ -34,7 +33,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should update a single rule property of name using a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_actions.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_actions.ts index d473863e7d028..bbd85e353e095 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_actions.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_actions.ts @@ -24,7 +24,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); - const es = getService('es'); describe('add_actions', () => { describe('adding actions', () => { @@ -34,7 +33,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should be able to create a new webhook action and attach it to a rule', async () => { @@ -60,7 +59,7 @@ export default ({ getService }: FtrProviderContext) => { .send(getWebHookAction()) .expect(200); - const rule = await createRule(supertest, getRuleWithWebHookAction(hookAction.id)); + const rule = await createRule(supertest, getRuleWithWebHookAction(hookAction.id, true)); await waitForRuleSuccess(supertest, rule.id); // expected result for status should be 'succeeded' @@ -82,7 +81,7 @@ export default ({ getService }: FtrProviderContext) => { // create a rule with the action attached and a meta field const ruleWithAction: CreateRulesSchema = { - ...getRuleWithWebHookAction(hookAction.id), + ...getRuleWithWebHookAction(hookAction.id, true), meta: {}, }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_prepackaged_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_prepackaged_rules.ts index c889e152759a8..b653d46905503 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_prepackaged_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_prepackaged_rules.ts @@ -5,6 +5,7 @@ */ import expect from '@kbn/expect'; +import { PrePackagedRulesAndTimelinesSchema } from '../../../../plugins/security_solution/common/detection_engine/schemas/response'; import { DETECTION_ENGINE_PREPACKAGED_URL } from '../../../../plugins/security_solution/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -13,6 +14,7 @@ import { deleteAllAlerts, deleteAllTimelines, deleteSignalsIndex, + installPrePackagedRules, waitFor, } from '../../utils'; @@ -45,18 +47,27 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); await deleteAllTimelines(es); }); - it('should contain two output keys of rules_installed and rules_updated', async () => { - const { body } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - expect(Object.keys(body)).to.eql([ + it('should create the prepackaged rules and return a count greater than zero, rules_updated to be zero, and contain the correct keys', async () => { + let responseBody: unknown; + await waitFor(async () => { + const { body, status } = await supertest + .put(DETECTION_ENGINE_PREPACKAGED_URL) + .set('kbn-xsrf', 'true') + .send(); + if (status === 200) { + responseBody = body; + } + return status === 200; + }, DETECTION_ENGINE_PREPACKAGED_URL); + + const prepackagedRules = responseBody as PrePackagedRulesAndTimelinesSchema; + expect(prepackagedRules.rules_installed).to.be.greaterThan(0); + expect(prepackagedRules.rules_updated).to.eql(0); + expect(Object.keys(prepackagedRules)).to.eql([ 'rules_installed', 'rules_updated', 'timelines_installed', @@ -64,74 +75,34 @@ export default ({ getService }: FtrProviderContext): void => { ]); }); - it('should create the prepackaged rules and return a count greater than zero', async () => { - const { body } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - expect(body.rules_installed).to.be.greaterThan(0); - }); - - it('should create the prepackaged rules that the rules_updated is of size zero', async () => { - const { body } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - expect(body.rules_updated).to.eql(0); - }); - - it('should be possible to call the API twice and the second time the number of rules installed should be zero', async () => { - await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); + it('should be possible to call the API twice and the second time the number of rules installed should be zero as well as timeline', async () => { + await installPrePackagedRules(supertest); // NOTE: I call the GET call until eventually it becomes consistent and that the number of rules to install are zero. - // This is to reduce flakiness where it can for a short period of time try to install the same rule the same rule twice. + // This is to reduce flakiness where it can for a short period of time try to install the same rule twice. await waitFor(async () => { const { body } = await supertest .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) .set('kbn-xsrf', 'true') .expect(200); return body.rules_not_installed === 0; - }); - - const { body } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - expect(body.rules_installed).to.eql(0); - }); - - it('should be possible to call the API twice and the second time the number of timelines installed should be zero', async () => { - await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); + }, `${DETECTION_ENGINE_PREPACKAGED_URL}/_status`); + let responseBody: unknown; await waitFor(async () => { - const { body } = await supertest - .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) + const { body, status } = await supertest + .put(DETECTION_ENGINE_PREPACKAGED_URL) .set('kbn-xsrf', 'true') - .expect(200); - return body.timelines_not_installed === 0; - }); - - const { body } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - expect(body.timelines_installed).to.eql(0); + .send(); + if (status === 200) { + responseBody = body; + } + return status === 200; + }, DETECTION_ENGINE_PREPACKAGED_URL); + + const prepackagedRules = responseBody as PrePackagedRulesAndTimelinesSchema; + expect(prepackagedRules.rules_installed).to.eql(0); + expect(prepackagedRules.timelines_installed).to.eql(0); }); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_exceptions.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_exceptions.ts index 651a7601ca95a..7e4a6ad86cda5 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_exceptions.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_exceptions.ts @@ -32,7 +32,7 @@ import { createExceptionList, createExceptionListItem, waitForSignalsToBePresent, - getAllSignals, + getSignalsByIds, } from '../../utils'; // eslint-disable-next-line import/no-default-export @@ -49,7 +49,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); await deleteAllExceptions(es); }); @@ -101,6 +101,7 @@ export default ({ getService }: FtrProviderContext) => { const ruleWithException: CreateRulesSchema = { ...getSimpleRule(), + enabled: true, exceptions_list: [ { id, @@ -117,6 +118,7 @@ export default ({ getService }: FtrProviderContext) => { const expected: Partial = { ...getSimpleRuleOutput(), + enabled: true, exceptions_list: [ { id, @@ -397,7 +399,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); await deleteAllExceptions(es); await esArchiver.unload('auditbeat/hosts'); }); @@ -441,9 +443,10 @@ export default ({ getService }: FtrProviderContext) => { }, ], }; - await createRule(supertest, ruleWithException); - await waitForSignalsToBePresent(supertest, 10); - const signalsOpen = await getAllSignals(supertest); + const { id: createdId } = await createRule(supertest, ruleWithException); + await waitForRuleSuccess(supertest, createdId); + await waitForSignalsToBePresent(supertest, 10, [createdId]); + const signalsOpen = await getSignalsByIds(supertest, [createdId]); expect(signalsOpen.hits.hits.length).equal(10); }); @@ -488,7 +491,7 @@ export default ({ getService }: FtrProviderContext) => { }; const rule = await createRule(supertest, ruleWithException); await waitForRuleSuccess(supertest, rule.id); - const signalsOpen = await getAllSignals(supertest); + const signalsOpen = await getSignalsByIds(supertest, [rule.id]); expect(signalsOpen.hits.hits.length).equal(0); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts index a18faf8543042..0da12ebba055a 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts @@ -25,12 +25,12 @@ import { getSimpleMlRule, getSimpleMlRuleOutput, waitForRuleSuccess, + getRuleForSignalTesting, } from '../../utils'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); - const es = getService('es'); describe('create_rules', () => { describe('validation errors', () => { @@ -56,7 +56,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should create a single rule with a rule_id', async () => { @@ -90,7 +90,7 @@ export default ({ getService }: FtrProviderContext) => { this pops up again elsewhere. */ it('should create a single rule with a rule_id and validate it ran successfully', async () => { - const simpleRule = getSimpleRule(); + const simpleRule = getRuleForSignalTesting(['auditbeat-*']); const { body } = await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') @@ -105,8 +105,6 @@ export default ({ getService }: FtrProviderContext) => { .send({ ids: [body.id] }) .expect(200); - const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql(getSimpleRuleOutput()); expect(statusBody[body.id].current_status.status).to.eql('succeeded'); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts index 58790dbfb759c..7ea47312a5030 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts @@ -15,6 +15,7 @@ import { createSignalsIndex, deleteAllAlerts, deleteSignalsIndex, + getRuleForSignalTesting, getSimpleRule, getSimpleRuleOutput, getSimpleRuleOutputWithoutRuleId, @@ -27,7 +28,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); - const es = getService('es'); describe('create_rules_bulk', () => { describe('validation errors', () => { @@ -58,7 +58,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should create a single rule with a rule_id', async () => { @@ -92,7 +92,7 @@ export default ({ getService }: FtrProviderContext): void => { this pops up again elsewhere. */ it('should create a single rule with a rule_id and validate it ran successfully', async () => { - const simpleRule = getSimpleRule(); + const simpleRule = getRuleForSignalTesting(['auditbeat-*']); const { body } = await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_create`) .set('kbn-xsrf', 'true') @@ -107,8 +107,6 @@ export default ({ getService }: FtrProviderContext): void => { .send({ ids: [body[0].id] }) .expect(200); - const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect(bodyToCompare).to.eql(getSimpleRuleOutput()); expect(statusBody[body[0].id].current_status.status).to.eql('succeeded'); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts index 36cd8480998c5..21cfab3db6d6a 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts @@ -17,7 +17,7 @@ import { createSignalsIndex, deleteAllAlerts, deleteSignalsIndex, - getAllSignals, + getSignalsByIds, removeServerGeneratedProperties, waitForRuleSuccess, waitForSignalsToBePresent, @@ -30,7 +30,6 @@ import { getThreatMatchingSchemaPartialMock } from '../../../../plugins/security export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - const es = getService('es'); /** * Specific api integration tests for threat matching rule type @@ -59,7 +58,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should create a single rule with a rule_id', async () => { @@ -69,7 +68,10 @@ export default ({ getService }: FtrProviderContext) => { }); it('should create a single rule with a rule_id and validate it ran successfully', async () => { - const ruleResponse = await createRule(supertest, getCreateThreatMatchRulesSchemaMock()); + const ruleResponse = await createRule( + supertest, + getCreateThreatMatchRulesSchemaMock('rule-1', true) + ); await waitForRuleSuccess(supertest, ruleResponse.id); const { body: statusBody } = await supertest @@ -79,21 +81,21 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); const bodyToCompare = removeServerGeneratedProperties(ruleResponse); - expect(bodyToCompare).to.eql(getThreatMatchingSchemaPartialMock()); + expect(bodyToCompare).to.eql(getThreatMatchingSchemaPartialMock(true)); expect(statusBody[ruleResponse.id].current_status.status).to.eql('succeeded'); }); }); describe('tests with auditbeat data', () => { beforeEach(async () => { - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); await createSignalsIndex(supertest); await esArchiver.load('auditbeat/hosts'); }); afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); await esArchiver.unload('auditbeat/hosts'); }); @@ -125,9 +127,10 @@ export default ({ getService }: FtrProviderContext) => { threat_filters: [], }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 10); - const signalsOpen = await getAllSignals(supertest); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 10, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); expect(signalsOpen.hits.hits.length).equal(10); }); @@ -161,7 +164,7 @@ export default ({ getService }: FtrProviderContext) => { const ruleResponse = await createRule(supertest, rule); await waitForRuleSuccess(supertest, ruleResponse.id); - const signalsOpen = await getAllSignals(supertest); + const signalsOpen = await getSignalsByIds(supertest, [ruleResponse.id]); expect(signalsOpen.hits.hits.length).equal(0); }); @@ -199,7 +202,7 @@ export default ({ getService }: FtrProviderContext) => { const ruleResponse = await createRule(supertest, rule); await waitForRuleSuccess(supertest, ruleResponse.id); - const signalsOpen = await getAllSignals(supertest); + const signalsOpen = await getSignalsByIds(supertest, [ruleResponse.id]); expect(signalsOpen.hits.hits.length).equal(0); }); @@ -237,7 +240,7 @@ export default ({ getService }: FtrProviderContext) => { const ruleResponse = await createRule(supertest, rule); await waitForRuleSuccess(supertest, ruleResponse.id); - const signalsOpen = await getAllSignals(supertest); + const signalsOpen = await getSignalsByIds(supertest, [ruleResponse.id]); expect(signalsOpen.hits.hits.length).equal(0); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules.ts index 7104e16f438c6..786e953843210 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules.ts @@ -24,7 +24,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); - const es = getService('es'); describe('delete_rules', () => { describe('deleting rules', () => { @@ -34,7 +33,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should delete a single rule with a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules_bulk.ts index 35b31d2ccfefa..66aa43e8a3817 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules_bulk.ts @@ -24,7 +24,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); - const es = getService('es'); describe('delete_rules_bulk', () => { describe('deleting rules bulk using DELETE', () => { @@ -34,7 +33,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should delete a single rule with a rule_id', async () => { @@ -146,7 +145,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should delete a single rule with a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/README.md b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/README.md new file mode 100644 index 0000000000000..d6beb912d7007 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/README.md @@ -0,0 +1,21 @@ +These are tests for rule exception lists where we test each data type +* date +* double +* float +* integer +* ip +* keyword +* long +* text + +Against the operator types of: +* "is" +* "is not" +* "is one of" +* "is not one of" +* "exists" +* "does not exist" +* "is in list" +* "is not in list" + +If you add a test here, ensure you add it to the ./index.ts" file as well \ No newline at end of file diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/date.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/date.ts new file mode 100644 index 0000000000000..09cc470defa08 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/date.ts @@ -0,0 +1,611 @@ +/* + * 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 { + createListsIndex, + deleteAllExceptions, + deleteListsIndex, + importFile, +} from '../../../../lists_api_integration/utils'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { + createRule, + createRuleWithExceptionEntries, + createSignalsIndex, + deleteAllAlerts, + deleteSignalsIndex, + getRuleForSignalTesting, + getSignalsById, + waitForRuleSuccess, + waitForSignalsToBePresent, +} from '../../../utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + + describe('Rule exception operators for data type date', () => { + beforeEach(async () => { + await createSignalsIndex(supertest); + await createListsIndex(supertest); + await esArchiver.load('rule_exceptions/date'); + }); + + afterEach(async () => { + await deleteSignalsIndex(supertest); + await deleteAllAlerts(supertest); + await deleteAllExceptions(es); + await deleteListsIndex(supertest); + await esArchiver.unload('rule_exceptions/date'); + }); + + describe('"is" operator', () => { + it('should find all the dates from the data set when no exceptions are set on the rule', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql([ + '2020-10-01T05:08:53.000Z', + '2020-10-02T05:08:53.000Z', + '2020-10-03T05:08:53.000Z', + '2020-10-04T05:08:53.000Z', + ]); + }); + + it('should filter 1 single date if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + operator: 'included', + type: 'match', + value: '2020-10-01T05:08:53.000Z', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql([ + '2020-10-02T05:08:53.000Z', + '2020-10-03T05:08:53.000Z', + '2020-10-04T05:08:53.000Z', + ]); + }); + + it('should filter 2 dates if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + operator: 'included', + type: 'match', + value: '2020-10-01T05:08:53.000Z', + }, + ], + [ + { + field: 'date', + operator: 'included', + type: 'match', + value: '2020-10-02T05:08:53.000Z', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql(['2020-10-03T05:08:53.000Z', '2020-10-04T05:08:53.000Z']); + }); + + it('should filter 3 dates if all 3 are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + operator: 'included', + type: 'match', + value: '2020-10-01T05:08:53.000Z', + }, + ], + [ + { + field: 'date', + operator: 'included', + type: 'match', + value: '2020-10-02T05:08:53.000Z', + }, + ], + [ + { + field: 'date', + operator: 'included', + type: 'match', + value: '2020-10-03T05:08:53.000Z', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql(['2020-10-04T05:08:53.000Z']); + }); + + it('should filter 4 dates if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + operator: 'included', + type: 'match', + value: '2020-10-01T05:08:53.000Z', + }, + ], + [ + { + field: 'date', + operator: 'included', + type: 'match', + value: '2020-10-02T05:08:53.000Z', + }, + ], + [ + { + field: 'date', + operator: 'included', + type: 'match', + value: '2020-10-03T05:08:53.000Z', + }, + ], + [ + { + field: 'date', + operator: 'included', + type: 'match', + value: '2020-10-04T05:08:53.000Z', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is not" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + operator: 'excluded', + type: 'match', + value: '2021-10-01T05:08:53.000Z', // date is not in data set + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql([]); + }); + + it('will return just 1 result we excluded', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + operator: 'excluded', + type: 'match', + value: '2020-10-01T05:08:53.000Z', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql(['2020-10-01T05:08:53.000Z']); + }); + + it('will return 0 results if we exclude two dates', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + operator: 'excluded', + type: 'match', + value: '2020-10-01T05:08:53.000Z', + }, + ], + [ + { + field: 'date', + operator: 'excluded', + type: 'match', + value: '2020-10-02T05:08:53.000Z', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is one of" operator', () => { + it('should filter 1 single date if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + operator: 'included', + type: 'match_any', + value: ['2020-10-01T05:08:53.000Z'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql([ + '2020-10-02T05:08:53.000Z', + '2020-10-03T05:08:53.000Z', + '2020-10-04T05:08:53.000Z', + ]); + }); + + it('should filter 2 dates if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + operator: 'included', + type: 'match_any', + value: ['2020-10-01T05:08:53.000Z', '2020-10-02T05:08:53.000Z'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql(['2020-10-03T05:08:53.000Z', '2020-10-04T05:08:53.000Z']); + }); + + it('should filter 3 dates if all 3 are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + operator: 'included', + type: 'match_any', + value: [ + '2020-10-01T05:08:53.000Z', + '2020-10-02T05:08:53.000Z', + '2020-10-03T05:08:53.000Z', + ], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql(['2020-10-04T05:08:53.000Z']); + }); + + it('should filter 4 dates if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + operator: 'included', + type: 'match_any', + value: [ + '2020-10-01T05:08:53.000Z', + '2020-10-02T05:08:53.000Z', + '2020-10-03T05:08:53.000Z', + '2020-10-04T05:08:53.000Z', + ], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is not one of" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + operator: 'excluded', + type: 'match_any', + value: ['2021-10-01T05:08:53.000Z', '2022-10-01T05:08:53.000Z'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql([]); + }); + + it('will return just the result we excluded', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + operator: 'excluded', + type: 'match_any', + value: ['2020-10-01T05:08:53.000Z', '2020-10-04T05:08:53.000Z'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql(['2020-10-01T05:08:53.000Z', '2020-10-04T05:08:53.000Z']); + }); + }); + + describe('"exists" operator', () => { + it('will return 0 results if matching against date', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + operator: 'included', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"does not exist" operator', () => { + it('will return 4 results if matching against date', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + operator: 'excluded', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql([ + '2020-10-01T05:08:53.000Z', + '2020-10-02T05:08:53.000Z', + '2020-10-03T05:08:53.000Z', + '2020-10-04T05:08:53.000Z', + ]); + }); + }); + + describe('"is in list" operator', () => { + it('will return 3 results if we have a list that includes 1 date', async () => { + await importFile(supertest, 'date', ['2020-10-01T05:08:53.000Z'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + list: { + id: 'list_items.txt', + type: 'date', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql([ + '2020-10-02T05:08:53.000Z', + '2020-10-03T05:08:53.000Z', + '2020-10-04T05:08:53.000Z', + ]); + }); + + it('will return 2 results if we have a list that includes 2 dates', async () => { + await importFile( + supertest, + 'date', + ['2020-10-01T05:08:53.000Z', '2020-10-03T05:08:53.000Z'], + 'list_items.txt' + ); + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + list: { + id: 'list_items.txt', + type: 'date', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql(['2020-10-02T05:08:53.000Z', '2020-10-04T05:08:53.000Z']); + }); + + it('will return 0 results if we have a list that includes all dates', async () => { + await importFile( + supertest, + 'date', + [ + '2020-10-01T05:08:53.000Z', + '2020-10-02T05:08:53.000Z', + '2020-10-03T05:08:53.000Z', + '2020-10-04T05:08:53.000Z', + ], + 'list_items.txt' + ); + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + list: { + id: 'list_items.txt', + type: 'date', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is not in list" operator', () => { + it('will return 1 result if we have a list that excludes 1 date', async () => { + await importFile(supertest, 'date', ['2020-10-01T05:08:53.000Z'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + list: { + id: 'list_items.txt', + type: 'date', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql(['2020-10-01T05:08:53.000Z']); + }); + + it('will return 2 results if we have a list that excludes 2 dates', async () => { + await importFile( + supertest, + 'date', + ['2020-10-01T05:08:53.000Z', '2020-10-03T05:08:53.000Z'], + 'list_items.txt' + ); + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + list: { + id: 'list_items.txt', + type: 'date', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql(['2020-10-01T05:08:53.000Z', '2020-10-03T05:08:53.000Z']); + }); + + it('will return 4 results if we have a list that excludes all dates', async () => { + await importFile( + supertest, + 'date', + [ + '2020-10-01T05:08:53.000Z', + '2020-10-02T05:08:53.000Z', + '2020-10-03T05:08:53.000Z', + '2020-10-04T05:08:53.000Z', + ], + 'list_items.txt' + ); + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + list: { + id: 'list_items.txt', + type: 'date', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql([ + '2020-10-01T05:08:53.000Z', + '2020-10-02T05:08:53.000Z', + '2020-10-03T05:08:53.000Z', + '2020-10-04T05:08:53.000Z', + ]); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/double.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/double.ts new file mode 100644 index 0000000000000..e29487880de6b --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/double.ts @@ -0,0 +1,744 @@ +/* + * 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 { + createListsIndex, + deleteAllExceptions, + deleteListsIndex, + importFile, +} from '../../../../lists_api_integration/utils'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { + createRule, + createRuleWithExceptionEntries, + createSignalsIndex, + deleteAllAlerts, + deleteSignalsIndex, + getRuleForSignalTesting, + getSignalsById, + waitForRuleSuccess, + waitForSignalsToBePresent, +} from '../../../utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + + describe('Rule exception operators for data type double', () => { + beforeEach(async () => { + await createSignalsIndex(supertest); + await createListsIndex(supertest); + await esArchiver.load('rule_exceptions/double'); + await esArchiver.load('rule_exceptions/double_as_string'); + }); + + afterEach(async () => { + await deleteSignalsIndex(supertest); + await deleteAllAlerts(supertest); + await deleteAllExceptions(es); + await deleteListsIndex(supertest); + await esArchiver.unload('rule_exceptions/double'); + await esArchiver.unload('rule_exceptions/double_as_string'); + }); + + describe('"is" operator', () => { + it('should find all the double from the data set when no exceptions are set on the rule', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); + }); + + it('should filter 1 single double if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + operator: 'included', + type: 'match', + value: '1.0', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.1', '1.2', '1.3']); + }); + + it('should filter 2 double if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + operator: 'included', + type: 'match', + value: '1.0', + }, + ], + [ + { + field: 'double', + operator: 'included', + type: 'match', + value: '1.1', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.2', '1.3']); + }); + + it('should filter 3 double if all 3 are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + operator: 'included', + type: 'match', + value: '1.0', + }, + ], + [ + { + field: 'double', + operator: 'included', + type: 'match', + value: '1.1', + }, + ], + [ + { + field: 'double', + operator: 'included', + type: 'match', + value: '1.2', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.3']); + }); + + it('should filter 4 double if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + operator: 'included', + type: 'match', + value: '1.0', + }, + ], + [ + { + field: 'double', + operator: 'included', + type: 'match', + value: '1.1', + }, + ], + [ + { + field: 'double', + operator: 'included', + type: 'match', + value: '1.2', + }, + ], + [ + { + field: 'double', + operator: 'included', + type: 'match', + value: '1.3', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is not" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + operator: 'excluded', + type: 'match', + value: '500.0', // this value is not in the data set + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql([]); + }); + + it('will return just 1 result we excluded', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + operator: 'excluded', + type: 'match', + value: '1.0', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.0']); + }); + + it('will return 0 results if we exclude two double', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + operator: 'excluded', + type: 'match', + value: '1.0', + }, + ], + [ + { + field: 'double', + operator: 'excluded', + type: 'match', + value: '1.1', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is one of" operator', () => { + it('should filter 1 single double if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + operator: 'included', + type: 'match_any', + value: ['1.0'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.1', '1.2', '1.3']); + }); + + it('should filter 2 double if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + operator: 'included', + type: 'match_any', + value: ['1.0', '1.1'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.2', '1.3']); + }); + + it('should filter 3 double if all 3 are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + operator: 'included', + type: 'match_any', + value: ['1.0', '1.1', '1.2'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.3']); + }); + + it('should filter 4 double if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + operator: 'included', + type: 'match_any', + value: ['1.0', '1.1', '1.2', '1.3'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is not one of" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + operator: 'excluded', + type: 'match_any', + value: ['500', '600'], // both these values are not in the data set + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql([]); + }); + + it('will return just the result we excluded', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + operator: 'excluded', + type: 'match_any', + value: ['1.0', '1.3'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.0', '1.3']); + }); + }); + + describe('"exists" operator', () => { + it('will return 0 results if matching against double', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + operator: 'included', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"does not exist" operator', () => { + it('will return 4 results if matching against double', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + operator: 'excluded', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); + }); + }); + + describe('"is in list" operator', () => { + // TODO: Enable this test once the bugs are fixed, we cannot use a list of strings that represent + // a double against an index that has the doubles stored as real doubles. + describe.skip('working against double values in the data set', () => { + it('will return 3 results if we have a list that includes 1 double', async () => { + await importFile(supertest, 'double', ['1.0'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + list: { + id: 'list_items.txt', + type: 'double', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.1', '1.2', '1.3']); + }); + + it('will return 2 results if we have a list that includes 2 double', async () => { + await importFile(supertest, 'double', ['1.0', '1.2'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + list: { + id: 'list_items.txt', + type: 'double', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.1', '1.3']); + }); + + it('will return 0 results if we have a list that includes all double', async () => { + await importFile(supertest, 'double', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + list: { + id: 'list_items.txt', + type: 'double', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('working against string values in the data set', () => { + it('will return 3 results if we have a list that includes 1 double', async () => { + await importFile(supertest, 'double', ['1.0'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['double_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + list: { + id: 'list_items.txt', + type: 'double', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.1', '1.2', '1.3']); + }); + + it('will return 2 results if we have a list that includes 2 double', async () => { + await importFile(supertest, 'double', ['1.0', '1.2'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['double_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + list: { + id: 'list_items.txt', + type: 'double', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.1', '1.3']); + }); + + it('will return 0 results if we have a list that includes all double', async () => { + await importFile(supertest, 'double', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['double_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + list: { + id: 'list_items.txt', + type: 'double', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql([]); + }); + + // TODO: Fix this bug and then unskip this test + it.skip('will return 1 result if we have a list which contains the double range of 1.0-1.2', async () => { + await importFile(supertest, 'double_range', ['1.0-1.2'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['double_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(hits).to.eql(['1.3']); + }); + }); + }); + + describe('"is not in list" operator', () => { + // TODO: Enable this test once the bugs are fixed, we cannot use a list of strings that represent + // a double against an index that has the doubles stored as real doubles. + describe.skip('working against double values in the data set', () => { + it('will return 1 result if we have a list that excludes 1 double', async () => { + await importFile(supertest, 'double', ['1.0'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + list: { + id: 'list_items.txt', + type: 'double', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.0']); + }); + + it('will return 2 results if we have a list that excludes 2 double', async () => { + await importFile(supertest, 'double', ['1.0', '1.2'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + list: { + id: 'list_items.txt', + type: 'double', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.0', '1.2']); + }); + + it('will return 4 results if we have a list that excludes all double', async () => { + await importFile(supertest, 'double', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + list: { + id: 'list_items.txt', + type: 'double', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); + }); + }); + + describe('working against string values in the data set', () => { + it('will return 1 result if we have a list that excludes 1 double', async () => { + await importFile(supertest, 'double', ['1.0'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['double_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + list: { + id: 'list_items.txt', + type: 'double', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.0']); + }); + + it('will return 2 results if we have a list that excludes 2 double', async () => { + await importFile(supertest, 'double', ['1.0', '1.2'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['double_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + list: { + id: 'list_items.txt', + type: 'double', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.0', '1.2']); + }); + + it('will return 4 results if we have a list that excludes all double', async () => { + await importFile(supertest, 'double', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['double_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + list: { + id: 'list_items.txt', + type: 'double', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); + }); + + // TODO: Fix this bug and then unskip this test + it.skip('will return 3 results if we have a list which contains the double range of 1.0-1.2', async () => { + await importFile(supertest, 'double_range', ['1.0-1.2'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['double_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(hits).to.eql(['1.0', '1.1', '1.2']); + }); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/float.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/float.ts new file mode 100644 index 0000000000000..d68f0f6a69277 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/float.ts @@ -0,0 +1,744 @@ +/* + * 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 { + createListsIndex, + deleteAllExceptions, + deleteListsIndex, + importFile, +} from '../../../../lists_api_integration/utils'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { + createRule, + createRuleWithExceptionEntries, + createSignalsIndex, + deleteAllAlerts, + deleteSignalsIndex, + getRuleForSignalTesting, + getSignalsById, + waitForRuleSuccess, + waitForSignalsToBePresent, +} from '../../../utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + + describe('Rule exception operators for data type float', () => { + beforeEach(async () => { + await createSignalsIndex(supertest); + await createListsIndex(supertest); + await esArchiver.load('rule_exceptions/float'); + await esArchiver.load('rule_exceptions/float_as_string'); + }); + + afterEach(async () => { + await deleteSignalsIndex(supertest); + await deleteAllAlerts(supertest); + await deleteAllExceptions(es); + await deleteListsIndex(supertest); + await esArchiver.unload('rule_exceptions/float'); + await esArchiver.unload('rule_exceptions/float_as_string'); + }); + + describe('"is" operator', () => { + it('should find all the float from the data set when no exceptions are set on the rule', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); + }); + + it('should filter 1 single float if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + operator: 'included', + type: 'match', + value: '1.0', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.1', '1.2', '1.3']); + }); + + it('should filter 2 float if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + operator: 'included', + type: 'match', + value: '1.0', + }, + ], + [ + { + field: 'float', + operator: 'included', + type: 'match', + value: '1.1', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.2', '1.3']); + }); + + it('should filter 3 float if all 3 are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + operator: 'included', + type: 'match', + value: '1.0', + }, + ], + [ + { + field: 'float', + operator: 'included', + type: 'match', + value: '1.1', + }, + ], + [ + { + field: 'float', + operator: 'included', + type: 'match', + value: '1.2', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.3']); + }); + + it('should filter 4 float if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + operator: 'included', + type: 'match', + value: '1.0', + }, + ], + [ + { + field: 'float', + operator: 'included', + type: 'match', + value: '1.1', + }, + ], + [ + { + field: 'float', + operator: 'included', + type: 'match', + value: '1.2', + }, + ], + [ + { + field: 'float', + operator: 'included', + type: 'match', + value: '1.3', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is not" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + operator: 'excluded', + type: 'match', + value: '500.0', // this value is not in the data set + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql([]); + }); + + it('will return just 1 result we excluded', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + operator: 'excluded', + type: 'match', + value: '1.0', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.0']); + }); + + it('will return 0 results if we exclude two float', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + operator: 'excluded', + type: 'match', + value: '1.0', + }, + ], + [ + { + field: 'float', + operator: 'excluded', + type: 'match', + value: '1.1', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is one of" operator', () => { + it('should filter 1 single float if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + operator: 'included', + type: 'match_any', + value: ['1.0'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.1', '1.2', '1.3']); + }); + + it('should filter 2 float if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + operator: 'included', + type: 'match_any', + value: ['1.0', '1.1'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.2', '1.3']); + }); + + it('should filter 3 float if all 3 are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + operator: 'included', + type: 'match_any', + value: ['1.0', '1.1', '1.2'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.3']); + }); + + it('should filter 4 float if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + operator: 'included', + type: 'match_any', + value: ['1.0', '1.1', '1.2', '1.3'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is not one of" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + operator: 'excluded', + type: 'match_any', + value: ['500', '600'], // both these values are not in the data set + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql([]); + }); + + it('will return just the result we excluded', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + operator: 'excluded', + type: 'match_any', + value: ['1.0', '1.3'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.0', '1.3']); + }); + }); + + describe('"exists" operator', () => { + it('will return 0 results if matching against float', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + operator: 'included', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"does not exist" operator', () => { + it('will return 4 results if matching against float', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + operator: 'excluded', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); + }); + }); + + describe('"is in list" operator', () => { + // TODO: Enable this test once the bugs are fixed, we cannot use a list of strings that represent + // a float against an index that has the floats stored as real floats. + describe.skip('working against float values in the data set', () => { + it('will return 3 results if we have a list that includes 1 float', async () => { + await importFile(supertest, 'float', ['1.0'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + list: { + id: 'list_items.txt', + type: 'float', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.1', '1.2', '1.3']); + }); + + it('will return 2 results if we have a list that includes 2 float', async () => { + await importFile(supertest, 'float', ['1.0', '1.2'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + list: { + id: 'list_items.txt', + type: 'float', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.1', '1.3']); + }); + + it('will return 0 results if we have a list that includes all float', async () => { + await importFile(supertest, 'float', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + list: { + id: 'list_items.txt', + type: 'float', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('working against string values in the data set', () => { + it('will return 3 results if we have a list that includes 1 float', async () => { + await importFile(supertest, 'float', ['1.0'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['float_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + list: { + id: 'list_items.txt', + type: 'float', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.1', '1.2', '1.3']); + }); + + it('will return 2 results if we have a list that includes 2 float', async () => { + await importFile(supertest, 'float', ['1.0', '1.2'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['float_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + list: { + id: 'list_items.txt', + type: 'float', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.1', '1.3']); + }); + + it('will return 0 results if we have a list that includes all float', async () => { + await importFile(supertest, 'float', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['float_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + list: { + id: 'list_items.txt', + type: 'float', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql([]); + }); + + // TODO: Fix this bug and then unskip this test + it.skip('will return 1 result if we have a list which contains the float range of 1.0-1.2', async () => { + await importFile(supertest, 'float_range', ['1.0-1.2'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['float_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(hits).to.eql(['1.3']); + }); + }); + }); + + describe('"is not in list" operator', () => { + // TODO: Enable this test once the bugs are fixed, we cannot use a list of strings that represent + // a float against an index that has the floats stored as real floats. + describe.skip('working against float values in the data set', () => { + it('will return 1 result if we have a list that excludes 1 float', async () => { + await importFile(supertest, 'float', ['1.0'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + list: { + id: 'list_items.txt', + type: 'float', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.0']); + }); + + it('will return 2 results if we have a list that excludes 2 float', async () => { + await importFile(supertest, 'float', ['1.0', '1.2'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + list: { + id: 'list_items.txt', + type: 'float', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.0', '1.2']); + }); + + it('will return 4 results if we have a list that excludes all float', async () => { + await importFile(supertest, 'float', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + list: { + id: 'list_items.txt', + type: 'float', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); + }); + }); + + describe('working against string values in the data set', () => { + it('will return 1 result if we have a list that excludes 1 float', async () => { + await importFile(supertest, 'float', ['1.0'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['float_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + list: { + id: 'list_items.txt', + type: 'float', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.0']); + }); + + it('will return 2 results if we have a list that excludes 2 float', async () => { + await importFile(supertest, 'float', ['1.0', '1.2'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['float_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + list: { + id: 'list_items.txt', + type: 'float', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.0', '1.2']); + }); + + it('will return 4 results if we have a list that excludes all float', async () => { + await importFile(supertest, 'float', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['float_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + list: { + id: 'list_items.txt', + type: 'float', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); + }); + + // TODO: Fix this bug and then unskip this test + it.skip('will return 3 results if we have a list which contains the float range of 1.0-1.2', async () => { + await importFile(supertest, 'float_range', ['1.0-1.2'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['float_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(hits).to.eql(['1.0', '1.1', '1.2']); + }); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/index.ts new file mode 100644 index 0000000000000..d2aca34e27399 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/index.ts @@ -0,0 +1,23 @@ +/* + * 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('Detection exceptions data types and operators', function () { + this.tags('ciGroup1'); + + loadTestFile(require.resolve('./date')); + loadTestFile(require.resolve('./double')); + loadTestFile(require.resolve('./float')); + loadTestFile(require.resolve('./integer')); + loadTestFile(require.resolve('./ip')); + loadTestFile(require.resolve('./keyword')); + loadTestFile(require.resolve('./long')); + loadTestFile(require.resolve('./text')); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/integer.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/integer.ts new file mode 100644 index 0000000000000..9b38f0f7cbb42 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/integer.ts @@ -0,0 +1,744 @@ +/* + * 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 { + createListsIndex, + deleteAllExceptions, + deleteListsIndex, + importFile, +} from '../../../../lists_api_integration/utils'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { + createRule, + createRuleWithExceptionEntries, + createSignalsIndex, + deleteAllAlerts, + deleteSignalsIndex, + getRuleForSignalTesting, + getSignalsById, + waitForRuleSuccess, + waitForSignalsToBePresent, +} from '../../../utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + + describe('Rule exception operators for data type integer', () => { + beforeEach(async () => { + await createSignalsIndex(supertest); + await createListsIndex(supertest); + await esArchiver.load('rule_exceptions/integer'); + await esArchiver.load('rule_exceptions/integer_as_string'); + }); + + afterEach(async () => { + await deleteSignalsIndex(supertest); + await deleteAllAlerts(supertest); + await deleteAllExceptions(es); + await deleteListsIndex(supertest); + await esArchiver.unload('rule_exceptions/integer'); + await esArchiver.unload('rule_exceptions/integer_as_string'); + }); + + describe('"is" operator', () => { + it('should find all the integer from the data set when no exceptions are set on the rule', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['1', '2', '3', '4']); + }); + + it('should filter 1 single integer if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + operator: 'included', + type: 'match', + value: '1', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['2', '3', '4']); + }); + + it('should filter 2 integer if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + operator: 'included', + type: 'match', + value: '1', + }, + ], + [ + { + field: 'integer', + operator: 'included', + type: 'match', + value: '2', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['3', '4']); + }); + + it('should filter 3 integer if all 3 are as exceptions', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + operator: 'included', + type: 'match', + value: '1', + }, + ], + [ + { + field: 'integer', + operator: 'included', + type: 'match', + value: '2', + }, + ], + [ + { + field: 'integer', + operator: 'included', + type: 'match', + value: '3', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['4']); + }); + + it('should filter 4 integer if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + operator: 'included', + type: 'match', + value: '1', + }, + ], + [ + { + field: 'integer', + operator: 'included', + type: 'match', + value: '2', + }, + ], + [ + { + field: 'integer', + operator: 'included', + type: 'match', + value: '3', + }, + ], + [ + { + field: 'integer', + operator: 'included', + type: 'match', + value: '4', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is not" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + operator: 'excluded', + type: 'match', + value: '500.0', // this value is not in the data set + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql([]); + }); + + it('will return just 1 result we excluded', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + operator: 'excluded', + type: 'match', + value: '1', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['1']); + }); + + it('will return 0 results if we exclude two integer', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + operator: 'excluded', + type: 'match', + value: '1', + }, + ], + [ + { + field: 'integer', + operator: 'excluded', + type: 'match', + value: '2', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is one of" operator', () => { + it('should filter 1 single integer if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + operator: 'included', + type: 'match_any', + value: ['1'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['2', '3', '4']); + }); + + it('should filter 2 integer if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + operator: 'included', + type: 'match_any', + value: ['1', '2'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['3', '4']); + }); + + it('should filter 3 integer if all 3 are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + operator: 'included', + type: 'match_any', + value: ['1', '2', '3'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['4']); + }); + + it('should filter 4 integer if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + operator: 'included', + type: 'match_any', + value: ['1', '2', '3', '4'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is not one of" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + operator: 'excluded', + type: 'match_any', + value: ['500', '600'], // both these values are not in the data set + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql([]); + }); + + it('will return just the result we excluded', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + operator: 'excluded', + type: 'match_any', + value: ['1', '4'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['1', '4']); + }); + }); + + describe('"exists" operator', () => { + it('will return 0 results if matching against integer', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + operator: 'included', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"does not exist" operator', () => { + it('will return 4 results if matching against integer', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + operator: 'excluded', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['1', '2', '3', '4']); + }); + }); + + describe('"is in list" operator', () => { + // TODO: Enable this test once the bugs are fixed, we cannot use a list of strings that represent + // a integer against an index that has the integers stored as real integers. + describe.skip('working against integer values in the data set', () => { + it('will return 3 results if we have a list that includes 1 integer', async () => { + await importFile(supertest, 'integer', ['1'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + list: { + id: 'list_items.txt', + type: 'integer', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['2', '3', '4']); + }); + + it('will return 2 results if we have a list that includes 2 integer', async () => { + await importFile(supertest, 'integer', ['1', '3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + list: { + id: 'list_items.txt', + type: 'integer', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['2', '4']); + }); + + it('will return 0 results if we have a list that includes all integer', async () => { + await importFile(supertest, 'integer', ['1', '2', '3', '4'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + list: { + id: 'list_items.txt', + type: 'integer', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('working against string values in the data set', () => { + it('will return 3 results if we have a list that includes 1 integer', async () => { + await importFile(supertest, 'integer', ['1'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['integer_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + list: { + id: 'list_items.txt', + type: 'integer', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['2', '3', '4']); + }); + + it('will return 2 results if we have a list that includes 2 integer', async () => { + await importFile(supertest, 'integer', ['1', '3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['integer_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + list: { + id: 'list_items.txt', + type: 'integer', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['2', '4']); + }); + + it('will return 0 results if we have a list that includes all integer', async () => { + await importFile(supertest, 'integer', ['1', '2', '3', '4'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['integer_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + list: { + id: 'list_items.txt', + type: 'integer', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql([]); + }); + + // TODO: Fix this bug and then unskip this test + it.skip('will return 1 result if we have a list which contains the integer range of 1-3', async () => { + await importFile(supertest, 'integer_range', ['1-3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['integer_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(hits).to.eql(['4']); + }); + }); + }); + + describe('"is not in list" operator', () => { + // TODO: Enable this test once the bugs are fixed, we cannot use a list of strings that represent + // a integer against an index that has the integers stored as real integers. + describe.skip('working against integer values in the data set', () => { + it('will return 1 result if we have a list that excludes 1 integer', async () => { + await importFile(supertest, 'integer', ['1'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + list: { + id: 'list_items.txt', + type: 'integer', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['1']); + }); + + it('will return 2 results if we have a list that excludes 2 integer', async () => { + await importFile(supertest, 'integer', ['1', '3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + list: { + id: 'list_items.txt', + type: 'integer', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['1', '3']); + }); + + it('will return 4 results if we have a list that excludes all integer', async () => { + await importFile(supertest, 'integer', ['1', '2', '3', '4'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + list: { + id: 'list_items.txt', + type: 'integer', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['1', '2', '3', '4']); + }); + }); + + describe('working against string values in the data set', () => { + it('will return 1 result if we have a list that excludes 1 integer', async () => { + await importFile(supertest, 'integer', ['1'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['integer_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + list: { + id: 'list_items.txt', + type: 'integer', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['1']); + }); + + it('will return 2 results if we have a list that excludes 2 integer', async () => { + await importFile(supertest, 'integer', ['1', '3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['integer_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + list: { + id: 'list_items.txt', + type: 'integer', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['1', '3']); + }); + + it('will return 4 results if we have a list that excludes all integer', async () => { + await importFile(supertest, 'integer', ['1', '2', '3', '4'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['integer_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + list: { + id: 'list_items.txt', + type: 'integer', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['1', '2', '3', '4']); + }); + + // TODO: Fix this bug and then unskip this test + it.skip('will return 3 results if we have a list which contains the integer range of 1-3', async () => { + await importFile(supertest, 'integer_range', ['1-3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['integer_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(hits).to.eql(['1', '2', '3']); + }); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/ip.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/ip.ts new file mode 100644 index 0000000000000..c3537efc12de7 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/ip.ts @@ -0,0 +1,622 @@ +/* + * 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 { + createListsIndex, + deleteAllExceptions, + deleteListsIndex, + importFile, +} from '../../../../lists_api_integration/utils'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { + createRule, + createRuleWithExceptionEntries, + createSignalsIndex, + deleteAllAlerts, + deleteSignalsIndex, + getRuleForSignalTesting, + getSignalsById, + waitForRuleSuccess, + waitForSignalsToBePresent, +} from '../../../utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + + describe('Rule exception operators for data type ip', () => { + beforeEach(async () => { + await createSignalsIndex(supertest); + await createListsIndex(supertest); + await esArchiver.load('rule_exceptions/ip'); + }); + + afterEach(async () => { + await deleteSignalsIndex(supertest); + await deleteAllAlerts(supertest); + await deleteAllExceptions(es); + await deleteListsIndex(supertest); + await esArchiver.unload('rule_exceptions/ip'); + }); + + describe('"is" operator', () => { + it('should find all the ips from the data set when no exceptions are set on the rule', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4']); + }); + + it('should filter 1 single ip if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'included', + type: 'match', + value: '127.0.0.1', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.2', '127.0.0.3', '127.0.0.4']); + }); + + it('should filter 2 ips if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'included', + type: 'match', + value: '127.0.0.1', + }, + ], + [ + { + field: 'ip', + operator: 'included', + type: 'match', + value: '127.0.0.2', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.3', '127.0.0.4']); + }); + + it('should filter 3 ips if all 3 are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'included', + type: 'match', + value: '127.0.0.1', + }, + ], + [ + { + field: 'ip', + operator: 'included', + type: 'match', + value: '127.0.0.2', + }, + ], + [ + { + field: 'ip', + operator: 'included', + type: 'match', + value: '127.0.0.3', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.4']); + }); + + it('should filter 4 ips if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'included', + type: 'match', + value: '127.0.0.1', + }, + ], + [ + { + field: 'ip', + operator: 'included', + type: 'match', + value: '127.0.0.2', + }, + ], + [ + { + field: 'ip', + operator: 'included', + type: 'match', + value: '127.0.0.3', + }, + ], + [ + { + field: 'ip', + operator: 'included', + type: 'match', + value: '127.0.0.4', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql([]); + }); + + it('should filter a CIDR range of 127.0.0.1/30', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'included', + type: 'match', + value: '127.0.0.1/30', // CIDR IP Range is 127.0.0.0 - 127.0.0.3 + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.4']); + }); + }); + + describe('"is not" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'excluded', + type: 'match', + value: '192.168.0.1', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql([]); + }); + + it('will return just 1 result we excluded', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'excluded', + type: 'match', + value: '127.0.0.1', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.1']); + }); + + it('will return 0 results if we exclude two ips', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'excluded', + type: 'match', + value: '127.0.0.1', + }, + ], + [ + { + field: 'ip', + operator: 'excluded', + type: 'match', + value: '127.0.0.2', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql([]); + }); + }); + + describe('"is one of" operator', () => { + it('should filter 1 single ip if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'included', + type: 'match_any', + value: ['127.0.0.1'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.2', '127.0.0.3', '127.0.0.4']); + }); + + it('should filter 2 ips if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'included', + type: 'match_any', + value: ['127.0.0.1', '127.0.0.2'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.3', '127.0.0.4']); + }); + + it('should filter 3 ips if all 3 are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'included', + type: 'match_any', + value: ['127.0.0.1', '127.0.0.2', '127.0.0.3'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.4']); + }); + + it('should filter 4 ips if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'included', + type: 'match_any', + value: ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql([]); + }); + }); + + describe('"is not one of" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'excluded', + type: 'match_any', + value: ['192.168.0.1', '192.168.0.2'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql([]); + }); + + it('will return just the result we excluded', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'excluded', + type: 'match_any', + value: ['127.0.0.1', '127.0.0.4'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.1', '127.0.0.4']); + }); + }); + + describe('"exists" operator', () => { + it('will return 0 results if matching against ip', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'included', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql([]); + }); + }); + + describe('"does not exist" operator', () => { + it('will return 4 results if matching against ip', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'excluded', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4']); + }); + }); + + describe('"is in list" operator', () => { + it('will return 3 results if we have a list that includes 1 ip', async () => { + await importFile(supertest, 'ip', ['127.0.0.1'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.2', '127.0.0.3', '127.0.0.4']); + }); + + it('will return 2 results if we have a list that includes 2 ips', async () => { + await importFile(supertest, 'ip', ['127.0.0.1', '127.0.0.3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.2', '127.0.0.4']); + }); + + it('will return 0 results if we have a list that includes all ips', async () => { + await importFile( + supertest, + 'ip', + ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'], + 'list_items.txt' + ); + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql([]); + }); + + // TODO: Fix this bug and then unskip this test + it.skip('will return 1 result if we have a list which contains the CIDR range of 127.0.0.1/30', async () => { + await importFile(supertest, 'ip_range', ['127.0.0.1/30'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.4']); + }); + }); + + describe('"is not in list" operator', () => { + it('will return 1 result if we have a list that excludes 1 ip', async () => { + await importFile(supertest, 'ip', ['127.0.0.1'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.1']); + }); + + it('will return 2 results if we have a list that excludes 2 ips', async () => { + await importFile(supertest, 'ip', ['127.0.0.1', '127.0.0.3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.1', '127.0.0.3']); + }); + + it('will return 4 results if we have a list that excludes all ips', async () => { + await importFile( + supertest, + 'ip', + ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'], + 'list_items.txt' + ); + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4']); + }); + + // TODO: Fix this bug and then unskip this test + it.skip('will return 3 results if we have a list which contains the CIDR range of 127.0.0.1/30', async () => { + await importFile(supertest, 'ip_range', ['127.0.0.1/30'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.1', '127.0.0.2', '127.0.0.3']); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/keyword.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/keyword.ts new file mode 100644 index 0000000000000..0c227c9acc38c --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/keyword.ts @@ -0,0 +1,555 @@ +/* + * 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 { + createListsIndex, + deleteAllExceptions, + deleteListsIndex, + importFile, +} from '../../../../lists_api_integration/utils'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { + createRule, + createRuleWithExceptionEntries, + createSignalsIndex, + deleteAllAlerts, + deleteSignalsIndex, + getRuleForSignalTesting, + getSignalsById, + waitForRuleSuccess, + waitForSignalsToBePresent, +} from '../../../utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + + describe('Rule exception operators for data type keyword', () => { + beforeEach(async () => { + await createSignalsIndex(supertest); + await createListsIndex(supertest); + await esArchiver.load('rule_exceptions/keyword'); + }); + + afterEach(async () => { + await deleteSignalsIndex(supertest); + await deleteAllAlerts(supertest); + await deleteAllExceptions(es); + await deleteListsIndex(supertest); + await esArchiver.unload('rule_exceptions/keyword'); + }); + + describe('"is" operator', () => { + it('should find all the keyword from the data set when no exceptions are set on the rule', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); + }); + + it('should filter 1 single keyword if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + operator: 'included', + type: 'match', + value: 'word one', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql(['word four', 'word three', 'word two']); + }); + + it('should filter 2 keyword if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + operator: 'included', + type: 'match', + value: 'word one', + }, + ], + [ + { + field: 'keyword', + operator: 'included', + type: 'match', + value: 'word two', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql(['word four', 'word three']); + }); + + it('should filter 3 keyword if all 3 are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + operator: 'included', + type: 'match', + value: 'word one', + }, + ], + [ + { + field: 'keyword', + operator: 'included', + type: 'match', + value: 'word two', + }, + ], + [ + { + field: 'keyword', + operator: 'included', + type: 'match', + value: 'word three', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql(['word four']); + }); + + it('should filter 4 keyword if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + operator: 'included', + type: 'match', + value: 'word one', + }, + ], + [ + { + field: 'keyword', + operator: 'included', + type: 'match', + value: 'word two', + }, + ], + [ + { + field: 'keyword', + operator: 'included', + type: 'match', + value: 'word three', + }, + ], + [ + { + field: 'keyword', + operator: 'included', + type: 'match', + value: 'word four', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is not" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + operator: 'excluded', + type: 'match', + value: '500.0', // this value is not in the data set + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql([]); + }); + + it('will return just 1 result we excluded', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + operator: 'excluded', + type: 'match', + value: 'word one', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql(['word one']); + }); + + it('will return 0 results if we exclude two keyword', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + operator: 'excluded', + type: 'match', + value: 'word one', + }, + ], + [ + { + field: 'keyword', + operator: 'excluded', + type: 'match', + value: 'word two', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is one of" operator', () => { + it('should filter 1 single keyword if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + operator: 'included', + type: 'match_any', + value: ['word one'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql(['word four', 'word three', 'word two']); + }); + + it('should filter 2 keyword if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + operator: 'included', + type: 'match_any', + value: ['word one', 'word two'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql(['word four', 'word three']); + }); + + it('should filter 3 keyword if all 3 are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + operator: 'included', + type: 'match_any', + value: ['word one', 'word three', 'word two'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql(['word four']); + }); + + it('should filter 4 keyword if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + operator: 'included', + type: 'match_any', + value: ['word four', 'word one', 'word three', 'word two'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is not one of" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + operator: 'excluded', + type: 'match_any', + value: ['500', '600'], // both these values are not in the data set + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql([]); + }); + + it('will return just the result we excluded', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + operator: 'excluded', + type: 'match_any', + value: ['word one', 'word four'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql(['word four', 'word one']); + }); + }); + + describe('"exists" operator', () => { + it('will return 0 results if matching against keyword', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + operator: 'included', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"does not exist" operator', () => { + it('will return 4 results if matching against keyword', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + operator: 'excluded', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); + }); + }); + + describe('"is in list" operator', () => { + it('will return 3 results if we have a list that includes 1 keyword', async () => { + await importFile(supertest, 'keyword', ['word one'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + list: { + id: 'list_items.txt', + type: 'keyword', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql(['word four', 'word three', 'word two']); + }); + + it('will return 2 results if we have a list that includes 2 keyword', async () => { + await importFile(supertest, 'keyword', ['word one', 'word three'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + list: { + id: 'list_items.txt', + type: 'keyword', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql(['word four', 'word two']); + }); + + it('will return 0 results if we have a list that includes all keyword', async () => { + await importFile( + supertest, + 'keyword', + ['word one', 'word two', 'word three', 'word four'], + 'list_items.txt' + ); + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + list: { + id: 'list_items.txt', + type: 'keyword', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is not in list" operator', () => { + it('will return 1 result if we have a list that excludes 1 keyword', async () => { + await importFile(supertest, 'keyword', ['word one'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + list: { + id: 'list_items.txt', + type: 'keyword', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql(['word one']); + }); + + it('will return 2 results if we have a list that excludes 2 keyword', async () => { + await importFile(supertest, 'keyword', ['word one', 'word three'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + list: { + id: 'list_items.txt', + type: 'keyword', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql(['word one', 'word three']); + }); + + it('will return 4 results if we have a list that excludes all keyword', async () => { + await importFile( + supertest, + 'keyword', + ['word one', 'word two', 'word three', 'word four'], + 'list_items.txt' + ); + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + list: { + id: 'list_items.txt', + type: 'keyword', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/long.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/long.ts new file mode 100644 index 0000000000000..5c110996c2198 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/long.ts @@ -0,0 +1,744 @@ +/* + * 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 { + createListsIndex, + deleteAllExceptions, + deleteListsIndex, + importFile, +} from '../../../../lists_api_integration/utils'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { + createRule, + createRuleWithExceptionEntries, + createSignalsIndex, + deleteAllAlerts, + deleteSignalsIndex, + getRuleForSignalTesting, + getSignalsById, + waitForRuleSuccess, + waitForSignalsToBePresent, +} from '../../../utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + + describe('Rule exception operators for data type long', () => { + beforeEach(async () => { + await createSignalsIndex(supertest); + await createListsIndex(supertest); + await esArchiver.load('rule_exceptions/long'); + await esArchiver.load('rule_exceptions/long_as_string'); + }); + + afterEach(async () => { + await deleteSignalsIndex(supertest); + await deleteAllAlerts(supertest); + await deleteAllExceptions(es); + await deleteListsIndex(supertest); + await esArchiver.unload('rule_exceptions/long'); + await esArchiver.unload('rule_exceptions/long_as_string'); + }); + + describe('"is" operator', () => { + it('should find all the long from the data set when no exceptions are set on the rule', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['1', '2', '3', '4']); + }); + + it('should filter 1 single long if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + operator: 'included', + type: 'match', + value: '1', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['2', '3', '4']); + }); + + it('should filter 2 long if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + operator: 'included', + type: 'match', + value: '1', + }, + ], + [ + { + field: 'long', + operator: 'included', + type: 'match', + value: '2', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['3', '4']); + }); + + it('should filter 3 long if all 3 are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + operator: 'included', + type: 'match', + value: '1', + }, + ], + [ + { + field: 'long', + operator: 'included', + type: 'match', + value: '2', + }, + ], + [ + { + field: 'long', + operator: 'included', + type: 'match', + value: '3', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['4']); + }); + + it('should filter 4 long if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + operator: 'included', + type: 'match', + value: '1', + }, + ], + [ + { + field: 'long', + operator: 'included', + type: 'match', + value: '2', + }, + ], + [ + { + field: 'long', + operator: 'included', + type: 'match', + value: '3', + }, + ], + [ + { + field: 'long', + operator: 'included', + type: 'match', + value: '4', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is not" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + operator: 'excluded', + type: 'match', + value: '500.0', // this value is not in the data set + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql([]); + }); + + it('will return just 1 result we excluded', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + operator: 'excluded', + type: 'match', + value: '1', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['1']); + }); + + it('will return 0 results if we exclude two long', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + operator: 'excluded', + type: 'match', + value: '1', + }, + ], + [ + { + field: 'long', + operator: 'excluded', + type: 'match', + value: '2', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is one of" operator', () => { + it('should filter 1 single long if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + operator: 'included', + type: 'match_any', + value: ['1'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['2', '3', '4']); + }); + + it('should filter 2 long if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + operator: 'included', + type: 'match_any', + value: ['1', '2'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['3', '4']); + }); + + it('should filter 3 long if all 3 are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + operator: 'included', + type: 'match_any', + value: ['1', '2', '3'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['4']); + }); + + it('should filter 4 long if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + operator: 'included', + type: 'match_any', + value: ['1', '2', '3', '4'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is not one of" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + operator: 'excluded', + type: 'match_any', + value: ['500', '600'], // both these values are not in the data set + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql([]); + }); + + it('will return just the result we excluded', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + operator: 'excluded', + type: 'match_any', + value: ['1', '4'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['1', '4']); + }); + }); + + describe('"exists" operator', () => { + it('will return 0 results if matching against long', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + operator: 'included', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"does not exist" operator', () => { + it('will return 4 results if matching against long', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + operator: 'excluded', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['1', '2', '3', '4']); + }); + }); + + describe('"is in list" operator', () => { + // TODO: Enable this test once the bugs are fixed, we cannot use a list of strings that represent + // a long against an index that has the longs stored as real longs. + describe.skip('working against long values in the data set', () => { + it('will return 3 results if we have a list that includes 1 long', async () => { + await importFile(supertest, 'long', ['1'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + list: { + id: 'list_items.txt', + type: 'long', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['2', '3', '4']); + }); + + it('will return 2 results if we have a list that includes 2 long', async () => { + await importFile(supertest, 'long', ['1', '3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + list: { + id: 'list_items.txt', + type: 'long', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['2', '4']); + }); + + it('will return 0 results if we have a list that includes all long', async () => { + await importFile(supertest, 'long', ['1', '2', '3', '4'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + list: { + id: 'list_items.txt', + type: 'long', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('working against string values in the data set', () => { + it('will return 3 results if we have a list that includes 1 long', async () => { + await importFile(supertest, 'long', ['1'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['long_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + list: { + id: 'list_items.txt', + type: 'long', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['2', '3', '4']); + }); + + it('will return 2 results if we have a list that includes 2 long', async () => { + await importFile(supertest, 'long', ['1', '3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['long_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + list: { + id: 'list_items.txt', + type: 'long', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['2', '4']); + }); + + it('will return 0 results if we have a list that includes all long', async () => { + await importFile(supertest, 'long', ['1', '2', '3', '4'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['long_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + list: { + id: 'list_items.txt', + type: 'long', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql([]); + }); + + // TODO: Fix this bug and then unskip this test + it.skip('will return 1 result if we have a list which contains the long range of 1-3', async () => { + await importFile(supertest, 'long_range', ['1-3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['long_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(hits).to.eql(['4']); + }); + }); + }); + + describe('"is not in list" operator', () => { + // TODO: Enable this test once the bugs are fixed, we cannot use a list of strings that represent + // a long against an index that has the longs stored as real longs. + describe.skip('working against long values in the data set', () => { + it('will return 1 result if we have a list that excludes 1 long', async () => { + await importFile(supertest, 'long', ['1'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + list: { + id: 'list_items.txt', + type: 'long', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['1']); + }); + + it('will return 2 results if we have a list that excludes 2 long', async () => { + await importFile(supertest, 'long', ['1', '3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + list: { + id: 'list_items.txt', + type: 'long', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['1', '3']); + }); + + it('will return 4 results if we have a list that excludes all long', async () => { + await importFile(supertest, 'long', ['1', '2', '3', '4'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + list: { + id: 'list_items.txt', + type: 'long', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['1', '2', '3', '4']); + }); + }); + + describe('working against string values in the data set', () => { + it('will return 1 result if we have a list that excludes 1 long', async () => { + await importFile(supertest, 'long', ['1'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['long_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + list: { + id: 'list_items.txt', + type: 'long', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['1']); + }); + + it('will return 2 results if we have a list that excludes 2 long', async () => { + await importFile(supertest, 'long', ['1', '3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['long_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + list: { + id: 'list_items.txt', + type: 'long', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['1', '3']); + }); + + it('will return 4 results if we have a list that excludes all long', async () => { + await importFile(supertest, 'long', ['1', '2', '3', '4'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['long_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + list: { + id: 'list_items.txt', + type: 'long', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['1', '2', '3', '4']); + }); + + // TODO: Fix this bug and then unskip this test + it.skip('will return 3 results if we have a list which contains the long range of 1-3', async () => { + await importFile(supertest, 'long_range', ['1-3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['long_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(hits).to.eql(['1', '2', '3']); + }); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/text.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/text.ts new file mode 100644 index 0000000000000..d2066b1023d3c --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/text.ts @@ -0,0 +1,827 @@ +/* + * 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 { + createListsIndex, + deleteAllExceptions, + deleteListsIndex, + importFile, + importTextFile, +} from '../../../../lists_api_integration/utils'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { + createRule, + createRuleWithExceptionEntries, + createSignalsIndex, + deleteAllAlerts, + deleteSignalsIndex, + getRuleForSignalTesting, + getSignalsById, + waitForRuleSuccess, + waitForSignalsToBePresent, +} from '../../../utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + + describe('Rule exception operators for data type text', () => { + beforeEach(async () => { + await createSignalsIndex(supertest); + await createListsIndex(supertest); + await esArchiver.load('rule_exceptions/text'); + await esArchiver.load('rule_exceptions/text_no_spaces'); + }); + + afterEach(async () => { + await deleteSignalsIndex(supertest); + await deleteAllAlerts(supertest); + await deleteAllExceptions(es); + await deleteListsIndex(supertest); + await esArchiver.unload('rule_exceptions/text'); + await esArchiver.unload('rule_exceptions/text_no_spaces'); + }); + + describe('"is" operator', () => { + it('should find all the text from the data set when no exceptions are set on the rule', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); + }); + + it('should filter 1 single text if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'included', + type: 'match', + value: 'word one', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word four', 'word three', 'word two']); + }); + + it('should filter 2 text if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'included', + type: 'match', + value: 'word one', + }, + ], + [ + { + field: 'text', + operator: 'included', + type: 'match', + value: 'word two', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word four', 'word three']); + }); + + it('should filter 3 text if all 3 are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'included', + type: 'match', + value: 'word one', + }, + ], + [ + { + field: 'text', + operator: 'included', + type: 'match', + value: 'word two', + }, + ], + [ + { + field: 'text', + operator: 'included', + type: 'match', + value: 'word three', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word four']); + }); + + it('should filter 4 text if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'included', + type: 'match', + value: 'word one', + }, + ], + [ + { + field: 'text', + operator: 'included', + type: 'match', + value: 'word two', + }, + ], + [ + { + field: 'text', + operator: 'included', + type: 'match', + value: 'word three', + }, + ], + [ + { + field: 'text', + operator: 'included', + type: 'match', + value: 'word four', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql([]); + }); + + it('should filter 1 single text using a single word', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'included', + type: 'match', + value: 'one', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word four', 'word three', 'word two']); + }); + + it('should filter all words using a common piece of text', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'included', + type: 'match', + value: 'word', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql([]); + }); + + it('should filter 1 single text with punctuation added', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'included', + type: 'match', + value: 'one.', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word four', 'word three', 'word two']); + }); + }); + + describe('"is not" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'excluded', + type: 'match', + value: '500.0', // this value is not in the data set + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql([]); + }); + + it('will return just 1 result we excluded', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'excluded', + type: 'match', + value: 'word one', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word one']); + }); + + it('will return 0 results if we exclude two text', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'excluded', + type: 'match', + value: 'word one', + }, + ], + [ + { + field: 'text', + operator: 'excluded', + type: 'match', + value: 'word two', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql([]); + }); + + it('should filter 1 single text using a single word', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'excluded', + type: 'match', + value: 'one', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word one']); + }); + + it('should filter all words using a common piece of text', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'excluded', + type: 'match', + value: 'word', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); + }); + + it('should filter 1 single text with punctuation added', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'excluded', + type: 'match', + value: 'one.', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word one']); + }); + }); + + describe('"is one of" operator', () => { + it('should filter 1 single text if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'included', + type: 'match_any', + value: ['word one'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word four', 'word three', 'word two']); + }); + + it('should filter 2 text if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'included', + type: 'match_any', + value: ['word one', 'word two'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word four', 'word three']); + }); + + it('should filter 3 text if all 3 are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'included', + type: 'match_any', + value: ['word one', 'word three', 'word two'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word four']); + }); + + it('should filter 4 text if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'included', + type: 'match_any', + value: ['word four', 'word one', 'word three', 'word two'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is not one of" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'excluded', + type: 'match_any', + value: ['500', '600'], // both these values are not in the data set + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql([]); + }); + + it('will return just the result we excluded', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'excluded', + type: 'match_any', + value: ['word one', 'word four'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word four', 'word one']); + }); + }); + + describe('"exists" operator', () => { + it('will return 0 results if matching against text', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'included', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"does not exist" operator', () => { + it('will return 4 results if matching against text', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'excluded', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); + }); + }); + + describe('"is in list" operator', () => { + describe('working against text values without spaces', () => { + it('will return 3 results if we have a list that includes 1 text', async () => { + await importFile(supertest, 'text', ['one'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['text_no_spaces']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + list: { + id: 'list_items.txt', + type: 'text', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['four', 'three', 'two']); + }); + + it('will return 2 results if we have a list that includes 2 text', async () => { + await importFile(supertest, 'text', ['one', 'three'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['text_no_spaces']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + list: { + id: 'list_items.txt', + type: 'text', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['four', 'two']); + }); + + it('will return 0 results if we have a list that includes all text', async () => { + await importTextFile( + supertest, + 'text', + ['one', 'two', 'three', 'four'], + 'list_items.txt' + ); + const rule = getRuleForSignalTesting(['text_no_spaces']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + list: { + id: 'list_items.txt', + type: 'text', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql([]); + }); + }); + + // TODO: Unskip these once this is fixed + describe.skip('working against text values with spaces', () => { + it('will return 3 results if we have a list that includes 1 text', async () => { + await importFile(supertest, 'text', ['one'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + list: { + id: 'list_items.txt', + type: 'text', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word four', 'word three', 'word two']); + }); + + it('will return 2 results if we have a list that includes 2 text', async () => { + await importFile(supertest, 'text', ['one', 'three'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + list: { + id: 'list_items.txt', + type: 'text', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word four', 'word two']); + }); + + it('will return 0 results if we have a list that includes all text', async () => { + await importTextFile( + supertest, + 'text', + ['one', 'two', 'three', 'four'], + 'list_items.txt' + ); + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + list: { + id: 'list_items.txt', + type: 'text', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql([]); + }); + }); + }); + + describe('"is not in list" operator', () => { + describe('working against text values without spaces', () => { + it('will return 1 result if we have a list that excludes 1 text', async () => { + await importTextFile(supertest, 'text', ['one'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['text_no_spaces']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + list: { + id: 'list_items.txt', + type: 'text', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['one']); + }); + + it('will return 2 results if we have a list that excludes 2 text', async () => { + await importTextFile(supertest, 'text', ['one', 'three'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['text_no_spaces']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + list: { + id: 'list_items.txt', + type: 'text', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['one', 'three']); + }); + + it('will return 4 results if we have a list that excludes all text', async () => { + await importTextFile( + supertest, + 'text', + ['one', 'two', 'three', 'four'], + 'list_items.txt' + ); + const rule = getRuleForSignalTesting(['text_no_spaces']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + list: { + id: 'list_items.txt', + type: 'text', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['four', 'one', 'three', 'two']); + }); + }); + + // TODO: Unskip these once this is fixed + describe.skip('working against text values with spaces', () => { + it('will return 1 result if we have a list that excludes 1 text', async () => { + await importTextFile(supertest, 'text', ['one'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + list: { + id: 'list_items.txt', + type: 'text', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word one']); + }); + + it('will return 2 results if we have a list that excludes 2 text', async () => { + await importTextFile(supertest, 'text', ['one', 'three'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + list: { + id: 'list_items.txt', + type: 'text', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word one', 'word three']); + }); + + it('will return 4 results if we have a list that excludes all text', async () => { + await importTextFile( + supertest, + 'text', + ['one', 'two', 'three', 'four'], + 'list_items.txt' + ); + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + list: { + id: 'list_items.txt', + type: 'text', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); + }); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/export_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/export_rules.ts index 2610796bdc384..4f76a0544a152 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/export_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/export_rules.ts @@ -22,7 +22,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); - const es = getService('es'); describe('export_rules', () => { describe('exporting rules', () => { @@ -32,7 +31,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should set the response content types to be expected', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_rules.ts index f496d035d8e60..2f06a84c7223b 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_rules.ts @@ -23,7 +23,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); - const es = getService('es'); describe('find_rules', () => { beforeEach(async () => { @@ -32,7 +31,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should return an empty find body correctly if no rules are loaded', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_statuses.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_statuses.ts index fac1fbaaf9675..8bb4c45d91bdd 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_statuses.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_statuses.ts @@ -30,7 +30,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); await deleteAllRulesStatuses(es); }); @@ -64,7 +64,7 @@ export default ({ getService }: FtrProviderContext): void => { this pops up again elsewhere. */ it('should return a single rule status when a single rule is loaded from a find status with defaults added', async () => { - const resBody = await createRule(supertest, getSimpleRule()); + const resBody = await createRule(supertest, getSimpleRule('rule-1', true)); await waitForRuleSuccess(supertest, resBody.id); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts index f76bdb4ebc718..0db3013503a33 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts @@ -17,9 +17,11 @@ import { createSignalsIndex, deleteAllAlerts, deleteSignalsIndex, - getAllSignals, + getRuleForSignalTesting, + getSignalsByIds, getSignalsByRuleIds, getSimpleRule, + waitForRuleSuccess, waitForSignalsToBePresent, } from '../../utils'; @@ -33,17 +35,15 @@ export const ID = 'BhbXBmkBR346wHgn4PeZ'; export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - const es = getService('es'); describe('Generating signals from source indexes', () => { beforeEach(async () => { - await deleteAllAlerts(es); await createSignalsIndex(supertest); }); afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); describe('Signals from audit beat are of the expected structure', () => { @@ -57,37 +57,37 @@ export default ({ getService }: FtrProviderContext) => { it('should have the specific audit record for _id or none of these tests below will pass', async () => { const rule: QueryCreateSchema = { - ...getSimpleRule(), - from: '1900-01-01T00:00:00.000Z', + ...getRuleForSignalTesting(['auditbeat-*']), query: `_id:${ID}`, }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 1); - const signalsOpen = await getAllSignals(supertest); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); expect(signalsOpen.hits.hits.length).greaterThan(0); }); it('should have recorded the rule_id within the signal', async () => { const rule: QueryCreateSchema = { - ...getSimpleRule(), - from: '1900-01-01T00:00:00.000Z', + ...getRuleForSignalTesting(['auditbeat-*']), query: `_id:${ID}`, }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 1); - const signalsOpen = await getAllSignals(supertest); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); expect(signalsOpen.hits.hits[0]._source.signal.rule.rule_id).eql(getSimpleRule().rule_id); }); it('should query and get back expected signal structure using a basic KQL query', async () => { const rule: QueryCreateSchema = { - ...getSimpleRule(), - from: '1900-01-01T00:00:00.000Z', + ...getRuleForSignalTesting(['auditbeat-*']), query: `_id:${ID}`, }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 1); - const signalsOpen = await getAllSignals(supertest); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); // remove rule to cut down on touch points for test changes when the rule format changes const { rule: removedRule, ...signalNoRule } = signalsOpen.hits.hits[0]._source.signal; expect(signalNoRule).eql({ @@ -126,25 +126,23 @@ export default ({ getService }: FtrProviderContext) => { }); it('should query and get back expected signal structure when it is a signal on a signal', async () => { - // create a 1 signal from 1 auditbeat record const rule: QueryCreateSchema = { - ...getSimpleRule(), - from: '1900-01-01T00:00:00.000Z', + ...getRuleForSignalTesting(['auditbeat-*']), query: `_id:${ID}`, }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 1); + const { id: createdId } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, createdId); + await waitForSignalsToBePresent(supertest, 1, [createdId]); // Run signals on top of that 1 signal which should create a single signal (on top of) a signal const ruleForSignals: QueryCreateSchema = { - ...getSimpleRule(), + ...getRuleForSignalTesting([`${DEFAULT_SIGNALS_INDEX}*`]), rule_id: 'signal-on-signal', - index: [`${DEFAULT_SIGNALS_INDEX}*`], - from: '1900-01-01T00:00:00.000Z', - query: '*:*', }; - await createRule(supertest, ruleForSignals); - await waitForSignalsToBePresent(supertest, 2); + + const { id } = await createRule(supertest, ruleForSignals); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); // Get our single signal on top of a signal const signalsOpen = await getSignalsByRuleIds(supertest, ['signal-on-signal']); @@ -198,15 +196,15 @@ export default ({ getService }: FtrProviderContext) => { describe('EQL Rules', () => { it('generates signals from EQL sequences in the expected form', async () => { const rule: EqlCreateSchema = { - ...getSimpleRule(), - from: '1900-01-01T00:00:00.000Z', + ...getRuleForSignalTesting(['auditbeat-*']), rule_id: 'eql-rule', type: 'eql', language: 'eql', query: 'sequence by host.name [any where true] [any where true]', }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 1); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); const signals = await getSignalsByRuleIds(supertest, ['eql-rule']); const signal = signals.hits.hits[0]._source.signal; @@ -250,15 +248,15 @@ export default ({ getService }: FtrProviderContext) => { it('generates building block signals from EQL sequences in the expected form', async () => { const rule: EqlCreateSchema = { - ...getSimpleRule(), - from: '1900-01-01T00:00:00.000Z', + ...getRuleForSignalTesting(['auditbeat-*']), rule_id: 'eql-rule', type: 'eql', language: 'eql', query: 'sequence by host.name [any where true] [any where true]', }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 1); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsByRuleIds(supertest, ['eql-rule']); const sequenceSignal = signalsOpen.hits.hits.find( (signal) => signal._source.signal.depth === 2 @@ -337,40 +335,39 @@ export default ({ getService }: FtrProviderContext) => { it('should have the specific audit record for _id or none of these tests below will pass', async () => { const rule: QueryCreateSchema = { - ...getSimpleRule(), - index: ['signal_name_clash'], - from: '1900-01-01T00:00:00.000Z', + ...getRuleForSignalTesting(['signal_name_clash']), query: '_id:1', }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 1); - const signalsOpen = await getAllSignals(supertest); + + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); expect(signalsOpen.hits.hits.length).greaterThan(0); }); it('should have recorded the rule_id within the signal', async () => { const rule: QueryCreateSchema = { - ...getSimpleRule(), - index: ['signal_name_clash'], - from: '1900-01-01T00:00:00.000Z', + ...getRuleForSignalTesting(['signal_name_clash']), query: '_id:1', }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 1); - const signalsOpen = await getAllSignals(supertest); + + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); expect(signalsOpen.hits.hits[0]._source.signal.rule.rule_id).eql(getSimpleRule().rule_id); }); it('should query and get back expected signal structure using a basic KQL query', async () => { const rule: QueryCreateSchema = { - ...getSimpleRule(), - index: ['signal_name_clash'], - from: '1900-01-01T00:00:00.000Z', + ...getRuleForSignalTesting(['signal_name_clash']), query: '_id:1', }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 1); - const signalsOpen = await getAllSignals(supertest); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); // remove rule to cut down on touch points for test changes when the rule format changes const { rule: removedRule, ...signalNoRule } = signalsOpen.hits.hits[0]._source.signal; expect(signalNoRule).eql({ @@ -404,26 +401,22 @@ export default ({ getService }: FtrProviderContext) => { }); it('should query and get back expected signal structure when it is a signal on a signal', async () => { - // create a 1 signal from 1 auditbeat record const rule: QueryCreateSchema = { - ...getSimpleRule(), - index: ['signal_name_clash'], - from: '1900-01-01T00:00:00.000Z', - query: `_id:1`, + ...getRuleForSignalTesting(['signal_name_clash']), + query: '_id:1', }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 1); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); // Run signals on top of that 1 signal which should create a single signal (on top of) a signal const ruleForSignals: QueryCreateSchema = { - ...getSimpleRule(), + ...getRuleForSignalTesting([`${DEFAULT_SIGNALS_INDEX}*`]), rule_id: 'signal-on-signal', - index: [`${DEFAULT_SIGNALS_INDEX}*`], - from: '1900-01-01T00:00:00.000Z', - query: '*:*', }; - await createRule(supertest, ruleForSignals); - await waitForSignalsToBePresent(supertest, 2); + const { id: createdId } = await createRule(supertest, ruleForSignals); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [createdId]); // Get our single signal on top of a signal const signalsOpen = await getSignalsByRuleIds(supertest, ['signal-on-signal']); @@ -479,7 +472,7 @@ export default ({ getService }: FtrProviderContext) => { * You should see the "signal" object/clash being copied to "original_signal" underneath * the signal object and no errors when they do have a clash. */ - describe('Signals generated from name clashes', () => { + describe('Signals generated from object clashes', () => { beforeEach(async () => { await esArchiver.load('signals/object_clash'); }); @@ -490,40 +483,37 @@ export default ({ getService }: FtrProviderContext) => { it('should have the specific audit record for _id or none of these tests below will pass', async () => { const rule: QueryCreateSchema = { - ...getSimpleRule(), - index: ['signal_object_clash'], - from: '1900-01-01T00:00:00.000Z', + ...getRuleForSignalTesting(['signal_object_clash']), query: '_id:1', }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 1); - const signalsOpen = await getAllSignals(supertest); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); expect(signalsOpen.hits.hits.length).greaterThan(0); }); it('should have recorded the rule_id within the signal', async () => { const rule: QueryCreateSchema = { - ...getSimpleRule(), - index: ['signal_object_clash'], - from: '1900-01-01T00:00:00.000Z', + ...getRuleForSignalTesting(['signal_object_clash']), query: '_id:1', }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 1); - const signalsOpen = await getAllSignals(supertest); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); expect(signalsOpen.hits.hits[0]._source.signal.rule.rule_id).eql(getSimpleRule().rule_id); }); it('should query and get back expected signal structure using a basic KQL query', async () => { const rule: QueryCreateSchema = { - ...getSimpleRule(), - index: ['signal_object_clash'], - from: '1900-01-01T00:00:00.000Z', + ...getRuleForSignalTesting(['signal_object_clash']), query: '_id:1', }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 1); - const signalsOpen = await getAllSignals(supertest); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); // remove rule to cut down on touch points for test changes when the rule format changes const { rule: removedRule, ...signalNoRule } = signalsOpen.hits.hits[0]._source.signal; expect(signalNoRule).eql({ @@ -563,26 +553,22 @@ export default ({ getService }: FtrProviderContext) => { }); it('should query and get back expected signal structure when it is a signal on a signal', async () => { - // create a 1 signal from 1 auditbeat record const rule: QueryCreateSchema = { - ...getSimpleRule(), - index: ['signal_object_clash'], - from: '1900-01-01T00:00:00.000Z', - query: `_id:1`, + ...getRuleForSignalTesting(['signal_object_clash']), + query: '_id:1', }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 1); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); // Run signals on top of that 1 signal which should create a single signal (on top of) a signal const ruleForSignals: QueryCreateSchema = { - ...getSimpleRule(), + ...getRuleForSignalTesting([`${DEFAULT_SIGNALS_INDEX}*`]), rule_id: 'signal-on-signal', - index: [`${DEFAULT_SIGNALS_INDEX}*`], - from: '1900-01-01T00:00:00.000Z', - query: '*:*', }; - await createRule(supertest, ruleForSignals); - await waitForSignalsToBePresent(supertest, 2); + const { id: createdId } = await createRule(supertest, ruleForSignals); + await waitForRuleSuccess(supertest, createdId); + await waitForSignalsToBePresent(supertest, 1, [createdId]); // Get our single signal on top of a signal const signalsOpen = await getSignalsByRuleIds(supertest, ['signal-on-signal']); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/get_prepackaged_rules_status.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/get_prepackaged_rules_status.ts index 1bbfce42d2baa..c72b2e50b39fc 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/get_prepackaged_rules_status.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/get_prepackaged_rules_status.ts @@ -32,7 +32,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); await deleteAllTimelines(es); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/import_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/import_rules.ts index 664077d5a4fab..4ae953ead9df7 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/import_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/import_rules.ts @@ -23,7 +23,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); - const es = getService('es'); describe('import_rules', () => { describe('importing rules without an index', () => { @@ -39,7 +38,7 @@ export default ({ getService }: FtrProviderContext): void => { .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=rule-1`) .send(); return body.status_code === 404; - }); + }, `within should not create a rule if the index does not exist, ${DETECTION_ENGINE_RULES_URL}?rule_id=rule-1`); // Try to fetch the rule which should still be a 404 (not found) const { body } = await supertest.get(`${DETECTION_ENGINE_RULES_URL}?rule_id=rule-1`).send(); @@ -86,7 +85,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should set the response content types to be expected', async () => { @@ -129,7 +128,7 @@ export default ({ getService }: FtrProviderContext): void => { await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') - .attach('file', getSimpleRuleAsNdjson(['rule-1'], true), 'rules.ndjson') + .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') .expect(200); const { body } = await supertest @@ -138,7 +137,7 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql(getSimpleRuleOutput('rule-1')); + expect(bodyToCompare).to.eql(getSimpleRuleOutput('rule-1', false)); }); it('should be able to import two rules', async () => { @@ -243,7 +242,7 @@ export default ({ getService }: FtrProviderContext): void => { await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') - .attach('file', getSimpleRuleAsNdjson(['rule-1'], true), 'rules.ndjson') + .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') .expect(200); const simpleRule = getSimpleRule('rule-1'); @@ -335,17 +334,13 @@ export default ({ getService }: FtrProviderContext): void => { await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') - .attach('file', getSimpleRuleAsNdjson(['rule-1', 'rule-2'], true), 'rules.ndjson') + .attach('file', getSimpleRuleAsNdjson(['rule-1', 'rule-2']), 'rules.ndjson') .expect(200); await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') - .attach( - 'file', - getSimpleRuleAsNdjson(['rule-1', 'rule-2', 'rule-3'], true), - 'rules.ndjson' - ) + .attach('file', getSimpleRuleAsNdjson(['rule-1', 'rule-2', 'rule-3']), 'rules.ndjson') .expect(200); const { body: bodyOfRule1 } = await supertest diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts index 962ae53b1241f..97d5b079fd206 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts @@ -19,6 +19,7 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./create_exceptions')); loadTestFile(require.resolve('./delete_rules')); loadTestFile(require.resolve('./delete_rules_bulk')); + loadTestFile(require.resolve('./exception_operators_data_types/index')); loadTestFile(require.resolve('./export_rules')); loadTestFile(require.resolve('./find_rules')); loadTestFile(require.resolve('./find_statuses')); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/open_close_signals.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/open_close_signals.ts index bbc3943b75955..87e3b145ed6fd 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/open_close_signals.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/open_close_signals.ts @@ -18,12 +18,13 @@ import { deleteSignalsIndex, setSignalStatus, getSignalStatusEmptyResponse, - getSimpleRule, getQuerySignalIds, deleteAllAlerts, createRule, waitForSignalsToBePresent, - getAllSignals, + getSignalsByIds, + waitForRuleSuccess, + getRuleForSignalTesting, } from '../../utils'; import { createUserAndRole } from '../roles_users_utils'; import { ROLES } from '../../../../plugins/security_solution/common/test'; @@ -32,7 +33,6 @@ import { ROLES } from '../../../../plugins/security_solution/common/test'; export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - const es = getService('es'); const supertestWithoutAuth = getService('supertestWithoutAuth'); const securityService = getService('security'); @@ -69,29 +69,31 @@ export default ({ getService }: FtrProviderContext) => { describe('tests with auditbeat data', () => { beforeEach(async () => { - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); await createSignalsIndex(supertest); await esArchiver.load('auditbeat/hosts'); }); afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); await esArchiver.unload('auditbeat/hosts'); }); it('should be able to execute and get 10 signals', async () => { - const rule = { ...getSimpleRule(), from: '1900-01-01T00:00:00.000Z', query: '*:*' }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 10); - const signalsOpen = await getAllSignals(supertest); + const rule = getRuleForSignalTesting(['auditbeat-*']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 10, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); expect(signalsOpen.hits.hits.length).equal(10); }); it('should be have set the signals in an open state initially', async () => { - const rule = { ...getSimpleRule(), from: '1900-01-01T00:00:00.000Z', query: '*:*' }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest); - const signalsOpen = await getAllSignals(supertest); + const rule = getRuleForSignalTesting(['auditbeat-*']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 10, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); const everySignalOpen = signalsOpen.hits.hits.every( ({ _source: { @@ -103,10 +105,11 @@ export default ({ getService }: FtrProviderContext) => { }); it('should be able to get a count of 10 closed signals when closing 10', async () => { - const rule = { ...getSimpleRule(), from: '1900-01-01T00:00:00.000Z', query: '*:*' }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 10); - const signalsOpen = await getAllSignals(supertest); + const rule = getRuleForSignalTesting(['auditbeat-*']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 10, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); // set all of the signals to the state of closed. There is no reason to use a waitUntil here @@ -129,10 +132,11 @@ export default ({ getService }: FtrProviderContext) => { }); it('should be able close signals immediately and they all should be closed', async () => { - const rule = { ...getSimpleRule(), from: '1900-01-01T00:00:00.000Z', query: '*:*' }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest); - const signalsOpen = await getAllSignals(supertest); + const rule = getRuleForSignalTesting(['auditbeat-*']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); // set all of the signals to the state of closed. There is no reason to use a waitUntil here @@ -163,11 +167,12 @@ export default ({ getService }: FtrProviderContext) => { }); it('should NOT be able to close signals with t1 analyst user', async () => { - const rule = { ...getSimpleRule(), from: '1900-01-01T00:00:00.000Z', query: '*:*' }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest); + const rule = getRuleForSignalTesting(['auditbeat-*']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); await createUserAndRole(securityService, ROLES.t1_analyst); - const signalsOpen = await getAllSignals(supertest); + const signalsOpen = await getSignalsByIds(supertest, [id]); const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); // Try to set all of the signals to the state of closed. @@ -200,12 +205,13 @@ export default ({ getService }: FtrProviderContext) => { }); it('should be able to close signals with soc_manager user', async () => { - const rule = { ...getSimpleRule(), from: '1900-01-01T00:00:00.000Z', query: '*:*' }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest); + const rule = getRuleForSignalTesting(['auditbeat-*']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); const userAndRole = ROLES.soc_manager; await createUserAndRole(securityService, userAndRole); - const signalsOpen = await getAllSignals(supertest); + const signalsOpen = await getSignalsByIds(supertest, [id]); const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); // Try to set all of the signals to the state of closed. diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/patch_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/patch_rules.ts index dbe66741e06c7..4de8abefe16fc 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/patch_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/patch_rules.ts @@ -25,7 +25,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); - const es = getService('es'); describe('patch_rules', () => { describe('patch rules', () => { @@ -35,7 +34,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should patch a single rule property of name using a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/patch_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/patch_rules_bulk.ts index 69330a2bf682a..e32771d0d917c 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/patch_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/patch_rules_bulk.ts @@ -23,7 +23,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); - const es = getService('es'); describe('patch_rules_bulk', () => { describe('patch rules bulk', () => { @@ -33,7 +32,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should patch a single rule property of name using a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/read_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/read_rules.ts index cfccb7436ea20..1697554441c16 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/read_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/read_rules.ts @@ -24,7 +24,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); - const es = getService('es'); describe('read_rules', () => { describe('reading rules', () => { @@ -34,7 +33,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should be able to read a single rule using rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules.ts index 23a8776b14631..59dbe97557157 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules.ts @@ -27,7 +27,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); - const es = getService('es'); describe('update_rules', () => { describe('update rules', () => { @@ -37,7 +36,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should update a single rule property of name using a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules_bulk.ts index 22aa40b0721a4..c5b65039aa116 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules_bulk.ts @@ -24,7 +24,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); - const es = getService('es'); describe('update_rules_bulk', () => { describe('update rules bulk', () => { @@ -34,7 +33,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should update a single rule property of name using a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/utils.ts b/x-pack/test/detection_engine_api_integration/utils.ts index f458fe118dcf7..06d33da8f1f55 100644 --- a/x-pack/test/detection_engine_api_integration/utils.ts +++ b/x-pack/test/detection_engine_api_integration/utils.ts @@ -9,6 +9,8 @@ import { SuperTest } from 'supertest'; import supertestAsPromised from 'supertest-as-promised'; import { Context } from '@elastic/elasticsearch/lib/Transport'; import { SearchResponse } from 'elasticsearch'; +import { NonEmptyEntriesArray } from '../../plugins/lists/common/schemas'; +import { getCreateExceptionListDetectionSchemaMock } from '../../plugins/lists/common/schemas/request/create_exception_list_schema.mock'; import { CreateRulesSchema, UpdateRulesSchema, @@ -35,6 +37,7 @@ import { DETECTION_ENGINE_RULES_URL, INTERNAL_RULE_ID_KEY, } from '../../plugins/security_solution/common/constants'; +import { getCreateExceptionListItemMinimalSchemaMockWithoutId } from '../../plugins/lists/common/schemas/request/create_exception_list_item_schema.mock'; /** * This will remove server generated properties such as date times, etc... @@ -76,9 +79,9 @@ export const removeServerGeneratedPropertiesIncludingRuleId = ( /** * This is a typical simple rule for testing that is easy for most basic testing * @param ruleId - * @param enabled Enables the rule on creation or not. Defaulted to false to enable it on import + * @param enabled Enables the rule on creation or not. Defaulted to true. */ -export const getSimpleRule = (ruleId = 'rule-1', enabled = true): QueryCreateSchema => ({ +export const getSimpleRule = (ruleId = 'rule-1', enabled = false): QueryCreateSchema => ({ name: 'Simple Rule Query', description: 'Simple Rule Query', enabled, @@ -90,13 +93,39 @@ export const getSimpleRule = (ruleId = 'rule-1', enabled = true): QueryCreateSch query: 'user.name: root or user.name: admin', }); +/** + * This is a typical signal testing rule that is easy for most basic testing of output of signals. + * It starts out in an enabled true state. The from is set very far back to test the basics of signal + * creation and testing by getting all the signals at once. + * @param ruleId The optional ruleId which is rule-1 by default. + * @param enabled Enables the rule on creation or not. Defaulted to true. + */ +export const getRuleForSignalTesting = ( + index: string[], + ruleId = 'rule-1', + enabled = true +): QueryCreateSchema => ({ + name: 'Signal Testing Query', + description: 'Tests a simple query', + enabled, + risk_score: 1, + rule_id: ruleId, + severity: 'high', + index, + type: 'query', + query: '*:*', + from: '1900-01-01T00:00:00.000Z', +}); + /** * This is a typical simple rule for testing that is easy for most basic testing - * @param ruleId + * @param ruleId The rule id + * @param enabled Set to tru to enable it, by default it is off */ -export const getSimpleRuleUpdate = (ruleId = 'rule-1'): UpdateRulesSchema => ({ +export const getSimpleRuleUpdate = (ruleId = 'rule-1', enabled = false): UpdateRulesSchema => ({ name: 'Simple Rule Query', description: 'Simple Rule Query', + enabled, risk_score: 1, rule_id: ruleId, severity: 'high', @@ -107,11 +136,13 @@ export const getSimpleRuleUpdate = (ruleId = 'rule-1'): UpdateRulesSchema => ({ /** * This is a representative ML rule payload as expected by the server - * @param ruleId + * @param ruleId The rule id + * @param enabled Set to tru to enable it, by default it is off */ -export const getSimpleMlRule = (ruleId = 'rule-1'): CreateRulesSchema => ({ +export const getSimpleMlRule = (ruleId = 'rule-1', enabled = false): CreateRulesSchema => ({ name: 'Simple ML Rule', description: 'Simple Machine Learning Rule', + enabled, anomaly_threshold: 44, risk_score: 1, rule_id: ruleId, @@ -120,9 +151,15 @@ export const getSimpleMlRule = (ruleId = 'rule-1'): CreateRulesSchema => ({ type: 'machine_learning', }); -export const getSimpleMlRuleUpdate = (ruleId = 'rule-1'): UpdateRulesSchema => ({ +/** + * This is a representative ML rule payload as expected by the server for an update + * @param ruleId The rule id + * @param enabled Set to tru to enable it, by default it is off + */ +export const getSimpleMlRuleUpdate = (ruleId = 'rule-1', enabled = false): UpdateRulesSchema => ({ name: 'Simple ML Rule', description: 'Simple Machine Learning Rule', + enabled, anomaly_threshold: 44, risk_score: 1, rule_id: ruleId, @@ -160,6 +197,19 @@ export const getQuerySignalsRuleId = (ruleIds: string[]) => ({ }, }); +/** + * Given an array of ids for a test this will get the signals + * created from that rule's regular id. + * @param ruleIds The rule_id to search for signals + */ +export const getQuerySignalsId = (ids: string[]) => ({ + query: { + terms: { + 'signal.rule.id': ids, + }, + }, +}); + export const setSignalStatus = ({ signalIds, status, @@ -216,12 +266,12 @@ export const binaryToString = (res: any, callback: any): void => { * This is the typical output of a simple rule that Kibana will output with all the defaults * except for the server generated properties. Useful for testing end to end tests. */ -export const getSimpleRuleOutput = (ruleId = 'rule-1'): Partial => ({ +export const getSimpleRuleOutput = (ruleId = 'rule-1', enabled = false): Partial => ({ actions: [], author: [], created_by: 'elastic', description: 'Simple Rule Query', - enabled: true, + enabled, false_positives: [], from: 'now-6m', immutable: false, @@ -274,21 +324,38 @@ export const getSimpleMlRuleOutput = (ruleId = 'rule-1'): Partial = }; /** - * Remove all alerts from the .kibana index - * This will retry 20 times before giving up and hopefully still not interfere with other tests - * @param es The ElasticSearch handle + * Removes all rules by looping over any found and removing them from REST. + * @param supertest The supertest agent. */ -export const deleteAllAlerts = async (es: Client): Promise => { - return countDownES(async () => { - return es.deleteByQuery({ - index: '.kibana', - q: 'type:alert', - wait_for_completion: true, - refresh: true, - conflicts: 'proceed', - body: {}, - }); - }, 'deleteAllAlerts'); +export const deleteAllAlerts = async ( + supertest: SuperTest +): Promise => { + await countDownTest( + async () => { + const { body } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL}/_find?per_page=9999`) + .set('kbn-xsrf', 'true') + .send(); + + const ids = body.data.map((rule: FullResponseSchema) => ({ + id: rule.id, + })); + + await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) + .send(ids) + .set('kbn-xsrf', 'true'); + + const { body: finalCheck } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL}/_find`) + .set('kbn-xsrf', 'true') + .send(); + return finalCheck.data.length === 0; + }, + 'deleteAllAlerts', + 50, + 1000 + ); }; export const downgradeImmutableRule = async (es: Client, ruleId: string): Promise => { @@ -331,7 +398,7 @@ export const deleteAllTimelines = async (es: Client): Promise => { * This will retry 20 times before giving up and hopefully still not interfere with other tests * @param es The ElasticSearch handle */ -export const deleteAllRulesStatuses = async (es: Client, retryCount = 20): Promise => { +export const deleteAllRulesStatuses = async (es: Client): Promise => { return countDownES(async () => { return es.deleteByQuery({ index: '.kibana', @@ -585,8 +652,8 @@ export const getWebHookAction = () => ({ name: 'Some connector', }); -export const getRuleWithWebHookAction = (id: string): CreateRulesSchema => ({ - ...getSimpleRule(), +export const getRuleWithWebHookAction = (id: string, enabled = false): CreateRulesSchema => ({ + ...getSimpleRule('rule-1', enabled), throttle: 'rule', actions: [ { @@ -618,7 +685,8 @@ export const getSimpleRuleOutputWithWebHookAction = (actionId: string): Partial< // 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, + functionName: string, + maxTimeout: number = 10000, timeoutWait: number = 10 ): Promise => { await new Promise(async (resolve, reject) => { @@ -636,7 +704,9 @@ export const waitFor = async ( if (found) { resolve(); } else { - reject(new Error('timed out waiting for function condition to be true')); + reject( + new Error(`timed out waiting for function condition to be true within ${functionName}`) + ); } }); }; @@ -807,7 +877,7 @@ export const waitForRuleSuccess = async ( .send({ ids: [id] }) .expect(200); return body[id]?.current_status?.status === 'succeeded'; - }); + }, 'waitForRuleSuccess'); }; /** @@ -818,51 +888,77 @@ export const waitForRuleSuccess = async ( */ export const waitForSignalsToBePresent = async ( supertest: SuperTest, - numberOfSignals = 1 + numberOfSignals = 1, + signalIds: string[] ): Promise => { await waitFor(async () => { - const { - body: signalsOpen, - }: { body: SearchResponse<{ signal: Signal }> } = await supertest - .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) - .set('kbn-xsrf', 'true') - .send(getQueryAllSignals()) - .expect(200); + const signalsOpen = await getSignalsByIds(supertest, signalIds); return signalsOpen.hits.hits.length >= numberOfSignals; - }); + }, 'waitForSignalsToBePresent'); }; /** - * Returns all signals both closed and opened + * Returns all signals both closed and opened by ruleId * @param supertest Deps */ -export const getAllSignals = async ( - supertest: SuperTest +export const getSignalsByRuleIds = async ( + supertest: SuperTest, + ruleIds: string[] ): Promise< SearchResponse<{ signal: Signal; + [x: string]: unknown; }> > => { const { body: signalsOpen }: { body: SearchResponse<{ signal: Signal }> } = await supertest .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) .set('kbn-xsrf', 'true') - .send(getQueryAllSignals()) + .send(getQuerySignalsRuleId(ruleIds)) .expect(200); return signalsOpen; }; -export const getSignalsByRuleIds = async ( +/** + * Given an array of rule ids this will return only signals based on that rule id both + * open and closed + * @param supertest agent + * @param ids Array of the rule ids + */ +export const getSignalsByIds = async ( supertest: SuperTest, - ruleIds: string[] + ids: string[] ): Promise< SearchResponse<{ signal: Signal; + [x: string]: unknown; }> > => { const { body: signalsOpen }: { body: SearchResponse<{ signal: Signal }> } = await supertest .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) .set('kbn-xsrf', 'true') - .send(getQuerySignalsRuleId(ruleIds)) + .send(getQuerySignalsId(ids)) + .expect(200); + return signalsOpen; +}; + +/** + * Given a single rule id this will return only signals based on that rule id. + * @param supertest agent + * @param ids Rule id + */ +export const getSignalsById = async ( + supertest: SuperTest, + id: string +): Promise< + SearchResponse<{ + signal: Signal; + [x: string]: unknown; + }> +> => { + const { body: signalsOpen }: { body: SearchResponse<{ signal: Signal }> } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQuerySignalsId([id])) .expect(200); return signalsOpen; }; @@ -870,5 +966,77 @@ export const getSignalsByRuleIds = async ( export const installPrePackagedRules = async ( supertest: SuperTest ): Promise => { - await supertest.put(DETECTION_ENGINE_PREPACKAGED_URL).set('kbn-xsrf', 'true').send().expect(200); + await countDownTest(async () => { + const { status } = await supertest + .put(DETECTION_ENGINE_PREPACKAGED_URL) + .set('kbn-xsrf', 'true') + .send(); + return status === 200; + }, 'installPrePackagedRules'); +}; + +/** + * Convenience testing function where you can pass in just the entries and you will + * get a rule created with the entries added to an exception list and exception list item + * all auto-created at once. + * @param supertest super test agent + * @param rule The rule to create and attach an exception list to + * @param entries The entries to create the rule and exception list from + */ +export const createRuleWithExceptionEntries = async ( + supertest: SuperTest, + rule: QueryCreateSchema, + entries: NonEmptyEntriesArray[] +): Promise => { + // eslint-disable-next-line @typescript-eslint/naming-convention + const { id, list_id, namespace_type, type } = await createExceptionList( + supertest, + getCreateExceptionListDetectionSchemaMock() + ); + + await Promise.all( + entries.map((entry) => { + const exceptionListItem: CreateExceptionListItemSchema = { + ...getCreateExceptionListItemMinimalSchemaMockWithoutId(), + entries: entry, + }; + return createExceptionListItem(supertest, exceptionListItem); + }) + ); + + // To reduce the odds of in-determinism and/or bugs we ensure we have + // the same length of entries before continuing. + await waitFor(async () => { + const { body } = await supertest.get( + `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${ + getCreateExceptionListDetectionSchemaMock().list_id + }` + ); + return body.data.length === entries.length; + }, `within createRuleWithExceptionEntries ${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${getCreateExceptionListDetectionSchemaMock().list_id}`); + + // create the rule but don't run it immediately as running it immediately can cause + // the rule to sometimes not filter correctly the first time with an exception list + // or other timing issues. Then afterwards wait for the rule to have succeeded before + // returning. + const ruleWithException: QueryCreateSchema = { + ...rule, + enabled: false, + exceptions_list: [ + { + id, + list_id, + namespace_type, + type, + }, + ], + }; + const ruleResponse = await createRule(supertest, ruleWithException); + await supertest + .patch(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .send({ rule_id: ruleResponse.rule_id, enabled: true }) + .expect(200); + + return ruleResponse; }; diff --git a/x-pack/test/functional/es_archives/rule_exceptions/README.md b/x-pack/test/functional/es_archives/rule_exceptions/README.md new file mode 100644 index 0000000000000..1fbf4962d55fe --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/README.md @@ -0,0 +1,11 @@ +Within this folder is input test data for tests such as: + +```ts +security_and_spaces/tests/rule_exceptions.ts +``` + +where these are small ECS compliant input indexes that try to express tests that exercise different parts of +the detection engine around creating and validating that the exceptions part of the detection engine functions. +Compliant meaning that these might contain extra fields but should not clash with ECS. Nothing stopping anyone +from being ECS strict and not having additional extra fields but the extra fields and mappings are to just try +and keep these tests simple and small. diff --git a/x-pack/test/functional/es_archives/rule_exceptions/date/data.json b/x-pack/test/functional/es_archives/rule_exceptions/date/data.json new file mode 100644 index 0000000000000..dd1609070a19d --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/date/data.json @@ -0,0 +1,51 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "date", + "source": { + "@timestamp": "2020-10-28T05:00:53.000Z", + "date": "2020-10-01T05:08:53.000Z" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "date", + "source": { + "@timestamp": "2020-10-28T05:01:53.000Z", + "date": "2020-10-02T05:08:53.000Z" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "index": "date", + "source": { + "@timestamp": "2020-10-28T05:02:53.000Z", + "date": "2020-10-03T05:08:53.000Z" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "date", + "source": { + "@timestamp": "2020-10-28T05:03:53.000Z", + "date": "2020-10-04T05:08:53.000Z" + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/date/mappings.json b/x-pack/test/functional/es_archives/rule_exceptions/date/mappings.json new file mode 100644 index 0000000000000..28c0158cdc852 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/date/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "date", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "date": { "type": "date" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/double/data.json b/x-pack/test/functional/es_archives/rule_exceptions/double/data.json new file mode 100644 index 0000000000000..1f7a5969f5872 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/double/data.json @@ -0,0 +1,51 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "double", + "source": { + "@timestamp": "2020-10-28T05:00:53.000Z", + "double": 1.0 + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "double", + "source": { + "@timestamp": "2020-10-28T05:01:53.000Z", + "double": 1.1 + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "index": "double", + "source": { + "@timestamp": "2020-10-28T05:02:53.000Z", + "double": 1.2 + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "double", + "source": { + "@timestamp": "2020-10-28T05:03:53.000Z", + "double": 1.3 + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/double/mappings.json b/x-pack/test/functional/es_archives/rule_exceptions/double/mappings.json new file mode 100644 index 0000000000000..bd69ae19ed148 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/double/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "double", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "double": { "type": "double" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/double_as_string/data.json b/x-pack/test/functional/es_archives/rule_exceptions/double_as_string/data.json new file mode 100644 index 0000000000000..2bdd685fae4c9 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/double_as_string/data.json @@ -0,0 +1,51 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "double_as_string", + "source": { + "@timestamp": "2020-10-28T05:00:53.000Z", + "double": "1.0" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "double_as_string", + "source": { + "@timestamp": "2020-10-28T05:01:53.000Z", + "double": "1.1" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "index": "double_as_string", + "source": { + "@timestamp": "2020-10-28T05:02:53.000Z", + "double": "1.2" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "double_as_string", + "source": { + "@timestamp": "2020-10-28T05:03:53.000Z", + "double": "1.3" + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/double_as_string/mappings.json b/x-pack/test/functional/es_archives/rule_exceptions/double_as_string/mappings.json new file mode 100644 index 0000000000000..a3b3fc52325a5 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/double_as_string/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "double_as_string", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "double": { "type": "double" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/float/data.json b/x-pack/test/functional/es_archives/rule_exceptions/float/data.json new file mode 100644 index 0000000000000..888be5ff20a32 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/float/data.json @@ -0,0 +1,51 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "float", + "source": { + "@timestamp": "2020-10-28T05:00:53.000Z", + "float": 1.0 + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "float", + "source": { + "@timestamp": "2020-10-28T05:01:53.000Z", + "float": 1.1 + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "index": "float", + "source": { + "@timestamp": "2020-10-28T05:02:53.000Z", + "float": 1.2 + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "float", + "source": { + "@timestamp": "2020-10-28T05:03:53.000Z", + "float": 1.3 + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/float/mappings.json b/x-pack/test/functional/es_archives/rule_exceptions/float/mappings.json new file mode 100644 index 0000000000000..b0a7b1a7fc60c --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/float/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "float", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "float": { "type": "float" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/float_as_string/data.json b/x-pack/test/functional/es_archives/rule_exceptions/float_as_string/data.json new file mode 100644 index 0000000000000..4d8575d3ccb9c --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/float_as_string/data.json @@ -0,0 +1,51 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "float_as_string", + "source": { + "@timestamp": "2020-10-28T05:00:53.000Z", + "float": "1.0" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "float_as_string", + "source": { + "@timestamp": "2020-10-28T05:01:53.000Z", + "float": "1.1" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "index": "float_as_string", + "source": { + "@timestamp": "2020-10-28T05:02:53.000Z", + "float": "1.2" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "float_as_string", + "source": { + "@timestamp": "2020-10-28T05:03:53.000Z", + "float": "1.3" + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/float_as_string/mappings.json b/x-pack/test/functional/es_archives/rule_exceptions/float_as_string/mappings.json new file mode 100644 index 0000000000000..7e66ace5eb5c6 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/float_as_string/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "float_as_string", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "float": { "type": "float" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/integer/data.json b/x-pack/test/functional/es_archives/rule_exceptions/integer/data.json new file mode 100644 index 0000000000000..5e2f1295397e6 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/integer/data.json @@ -0,0 +1,51 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "integer", + "source": { + "@timestamp": "2020-10-28T05:00:53.000Z", + "integer": 1 + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "integer", + "source": { + "@timestamp": "2020-10-28T05:01:53.000Z", + "integer": 2 + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "index": "integer", + "source": { + "@timestamp": "2020-10-28T05:02:53.000Z", + "integer": 3 + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "integer", + "source": { + "@timestamp": "2020-10-28T05:03:53.000Z", + "integer": 4 + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/integer/mappings.json b/x-pack/test/functional/es_archives/rule_exceptions/integer/mappings.json new file mode 100644 index 0000000000000..a05f3ec4e3186 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/integer/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "integer", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "integer": { "type": "integer" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/integer_as_string/data.json b/x-pack/test/functional/es_archives/rule_exceptions/integer_as_string/data.json new file mode 100644 index 0000000000000..5d0ac56e27d00 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/integer_as_string/data.json @@ -0,0 +1,51 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "integer_as_string", + "source": { + "@timestamp": "2020-10-28T05:00:53.000Z", + "integer": "1" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "integer_as_string", + "source": { + "@timestamp": "2020-10-28T05:01:53.000Z", + "integer": "2" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "index": "integer_as_string", + "source": { + "@timestamp": "2020-10-28T05:02:53.000Z", + "integer": "3" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "integer_as_string", + "source": { + "@timestamp": "2020-10-28T05:03:53.000Z", + "integer": "4" + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/integer_as_string/mappings.json b/x-pack/test/functional/es_archives/rule_exceptions/integer_as_string/mappings.json new file mode 100644 index 0000000000000..e98d0d89214dd --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/integer_as_string/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "integer_as_string", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "integer": { "type": "integer" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/ip/data.json b/x-pack/test/functional/es_archives/rule_exceptions/ip/data.json new file mode 100644 index 0000000000000..5dde1cba8f884 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/ip/data.json @@ -0,0 +1,51 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "ip", + "source": { + "@timestamp": "2020-10-28T05:00:53.000Z", + "ip": "127.0.0.1" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "ip", + "source": { + "@timestamp": "2020-10-28T05:01:53.000Z", + "ip": "127.0.0.2" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "index": "ip", + "source": { + "@timestamp": "2020-10-28T05:02:53.000Z", + "ip": "127.0.0.3" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "ip", + "source": { + "@timestamp": "2020-10-28T05:03:53.000Z", + "ip": "127.0.0.4" + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/ip/mappings.json b/x-pack/test/functional/es_archives/rule_exceptions/ip/mappings.json new file mode 100644 index 0000000000000..ceb58bc933507 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/ip/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "ip", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "ip": { "type": "ip" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/keyword/data.json b/x-pack/test/functional/es_archives/rule_exceptions/keyword/data.json new file mode 100644 index 0000000000000..09c54843f32c9 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/keyword/data.json @@ -0,0 +1,51 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "keyword", + "source": { + "@timestamp": "2020-10-28T05:00:53.000Z", + "keyword": "word one" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "keyword", + "source": { + "@timestamp": "2020-10-28T05:01:53.000Z", + "keyword": "word two" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "index": "keyword", + "source": { + "@timestamp": "2020-10-28T05:02:53.000Z", + "keyword": "word three" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "keyword", + "source": { + "@timestamp": "2020-10-28T05:03:53.000Z", + "keyword": "word four" + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/keyword/mappings.json b/x-pack/test/functional/es_archives/rule_exceptions/keyword/mappings.json new file mode 100644 index 0000000000000..bc8becbe07f45 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/keyword/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "keyword", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "keyword": { "type": "keyword" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/long/data.json b/x-pack/test/functional/es_archives/rule_exceptions/long/data.json new file mode 100644 index 0000000000000..807314bd28173 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/long/data.json @@ -0,0 +1,51 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "long", + "source": { + "@timestamp": "2020-10-28T05:00:53.000Z", + "long": 1 + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "long", + "source": { + "@timestamp": "2020-10-28T05:01:53.000Z", + "long": 2 + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "index": "long", + "source": { + "@timestamp": "2020-10-28T05:02:53.000Z", + "long": 3 + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "long", + "source": { + "@timestamp": "2020-10-28T05:03:53.000Z", + "long": 4 + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/long/mappings.json b/x-pack/test/functional/es_archives/rule_exceptions/long/mappings.json new file mode 100644 index 0000000000000..75b156805af78 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/long/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "long", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "long": { "type": "long" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/long_as_string/data.json b/x-pack/test/functional/es_archives/rule_exceptions/long_as_string/data.json new file mode 100644 index 0000000000000..3604026d2cdb0 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/long_as_string/data.json @@ -0,0 +1,51 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "long_as_string", + "source": { + "@timestamp": "2020-10-28T05:00:53.000Z", + "long": "1" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "long_as_string", + "source": { + "@timestamp": "2020-10-28T05:01:53.000Z", + "long": "2" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "index": "long_as_string", + "source": { + "@timestamp": "2020-10-28T05:02:53.000Z", + "long": "3" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "long_as_string", + "source": { + "@timestamp": "2020-10-28T05:03:53.000Z", + "long": "4" + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/long_as_string/mappings.json b/x-pack/test/functional/es_archives/rule_exceptions/long_as_string/mappings.json new file mode 100644 index 0000000000000..8fe9af08127d1 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/long_as_string/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "long_as_string", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "long": { "type": "long" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/text/data.json b/x-pack/test/functional/es_archives/rule_exceptions/text/data.json new file mode 100644 index 0000000000000..8d3da48224cc3 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/text/data.json @@ -0,0 +1,51 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "text", + "source": { + "@timestamp": "2020-10-28T05:00:53.000Z", + "text": "word one" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "text", + "source": { + "@timestamp": "2020-10-28T05:01:53.000Z", + "text": "word two" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "index": "text", + "source": { + "@timestamp": "2020-10-28T05:02:53.000Z", + "text": "word three" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "text", + "source": { + "@timestamp": "2020-10-28T05:03:53.000Z", + "text": "word four" + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/text/mappings.json b/x-pack/test/functional/es_archives/rule_exceptions/text/mappings.json new file mode 100644 index 0000000000000..5d3304fc202d5 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/text/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "text", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "text": { "type": "text" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/text_no_spaces/data.json b/x-pack/test/functional/es_archives/rule_exceptions/text_no_spaces/data.json new file mode 100644 index 0000000000000..a0caf9d9eb2d3 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/text_no_spaces/data.json @@ -0,0 +1,51 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "text_no_spaces", + "source": { + "@timestamp": "2020-10-28T05:00:53.000Z", + "text": "one" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "text_no_spaces", + "source": { + "@timestamp": "2020-10-28T05:01:53.000Z", + "text": "two" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "index": "text_no_spaces", + "source": { + "@timestamp": "2020-10-28T05:02:53.000Z", + "text": "three" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "text_no_spaces", + "source": { + "@timestamp": "2020-10-28T05:03:53.000Z", + "text": "four" + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/text_no_spaces/mappings.json b/x-pack/test/functional/es_archives/rule_exceptions/text_no_spaces/mappings.json new file mode 100644 index 0000000000000..b981af7937124 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/text_no_spaces/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "text_no_spaces", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "text": { "type": "text" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/wildcard/data.json b/x-pack/test/functional/es_archives/rule_exceptions/wildcard/data.json new file mode 100644 index 0000000000000..40dd24f83c0d2 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/wildcard/data.json @@ -0,0 +1,51 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "wildcard", + "source": { + "@timestamp": "2020-10-28T05:00:53.000Z", + "wildcard": "word one" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "wildcard", + "source": { + "@timestamp": "2020-10-28T05:01:53.000Z", + "wildcard": "word two" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "wildcard": "wildcard", + "source": { + "@timestamp": "2020-10-28T05:02:53.000Z", + "wildcard": "word three" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "wildcard", + "source": { + "@timestamp": "2020-10-28T05:03:53.000Z", + "wildcard": "word four" + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/wildcard/mappings.json b/x-pack/test/functional/es_archives/rule_exceptions/wildcard/mappings.json new file mode 100644 index 0000000000000..1b6a697ecbb8f --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/wildcard/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "wildcard", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "wildcard": { "type": "wildcard" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/signals/README.md b/x-pack/test/functional/es_archives/signals/README.md new file mode 100644 index 0000000000000..4b147a414f8b3 --- /dev/null +++ b/x-pack/test/functional/es_archives/signals/README.md @@ -0,0 +1,22 @@ +Within this folder is input test data for tests such as: + +```ts +security_and_spaces/tests/generating_signals.ts +``` + +where these are small ECS compliant input indexes that try to express tests that exercise different parts of +the detection engine signals. Compliant meaning that these might contain extra fields but should not clash with ECS. +Nothing stopping anyone from being ECS strict and not having additional extra fields but the extra fields and mappings +are to just try and keep these tests simple and small. Examples are: + + +This is an ECS document that has a numeric name clash with a signal structure +``` +numeric_name_clash +``` + +This is an ECS document that has an object name clash with a signal structure +``` +object_clash +``` + 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 index 7b7a6173fb408..ae9814e603b74 100644 --- 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 @@ -94,7 +94,7 @@ export default ({ getService }: FtrProviderContext): void => { .get(`${LIST_ITEM_URL}?list_id=list_items.txt&value=127.0.0.1`) .send(); return status !== 404; - }); + }, `${LIST_ITEM_URL}?list_id=list_items.txt&value=127.0.0.1`); const { body } = await supertest .get(`${LIST_ITEM_URL}?list_id=list_items.txt&value=127.0.0.1`) .send() diff --git a/x-pack/test/lists_api_integration/utils.ts b/x-pack/test/lists_api_integration/utils.ts index 5870239b73ed1..224048e868d7f 100644 --- a/x-pack/test/lists_api_integration/utils.ts +++ b/x-pack/test/lists_api_integration/utils.ts @@ -8,13 +8,15 @@ import { SuperTest } from 'supertest'; import supertestAsPromised from 'supertest-as-promised'; import { Client } from '@elastic/elasticsearch'; +import { getImportListItemAsBuffer } from '../../plugins/lists/common/schemas/request/import_list_item_schema.mock'; import { ListItemSchema, ExceptionListSchema, ExceptionListItemSchema, + Type, } from '../../plugins/lists/common/schemas'; import { ListSchema } from '../../plugins/lists/common'; -import { LIST_INDEX } from '../../plugins/lists/common/constants'; +import { LIST_INDEX, LIST_ITEM_URL } from '../../plugins/lists/common/constants'; import { countDownES, countDownTest } from '../detection_engine_api_integration/utils'; /** @@ -109,6 +111,7 @@ export const removeExceptionListServerGeneratedProperties = ( // Similar to ReactJs's waitFor from here: https://testing-library.com/docs/dom-testing-library/api-async#waitfor export const waitFor = async ( functionToTest: () => Promise, + functionName: string, maxTimeout: number = 5000, timeoutWait: number = 10 ) => { @@ -127,7 +130,7 @@ export const waitFor = async ( if (found) { resolve(); } else { - reject(new Error('timed out waiting for function condition to be true')); + reject(new Error(`timed out waiting for function ${functionName} condition to be true`)); } }); }; @@ -164,3 +167,134 @@ export const deleteAllExceptions = async (es: Client): Promise => { }); }, 'deleteAllExceptions'); }; + +/** + * Convenience function for quickly importing a given type and contents and then + * waiting to ensure they're there before continuing + * @param supertest The super test agent + * @param type The type to import as + * @param contents The contents of the import + * @param fileName filename to import as + */ +export const importFile = async ( + supertest: SuperTest, + type: Type, + contents: string[], + fileName: string +): Promise => { + await supertest + .post(`${LIST_ITEM_URL}/_import?type=${type}`) + .set('kbn-xsrf', 'true') + .attach('file', getImportListItemAsBuffer(contents), fileName) + .expect('Content-Type', 'application/json; charset=utf-8') + .expect(200); + + // although we have pushed the list and its items, it is async so we + // have to wait for the contents before continuing + await waitForListItems(supertest, contents, fileName); +}; + +/** + * Convenience function for quickly importing a given type and contents and then + * waiting to ensure they're there before continuing. This specifically checks tokens + * from text file + * @param supertest The super test agent + * @param type The type to import as + * @param contents The contents of the import + * @param fileName filename to import as + */ +export const importTextFile = async ( + supertest: SuperTest, + type: Type, + contents: string[], + fileName: string +): Promise => { + await supertest + .post(`${LIST_ITEM_URL}/_import?type=${type}`) + .set('kbn-xsrf', 'true') + .attach('file', getImportListItemAsBuffer(contents), fileName) + .expect('Content-Type', 'application/json; charset=utf-8') + .expect(200); + + // although we have pushed the list and its items, it is async so we + // have to wait for the contents before continuing + await waitForTextListItems(supertest, contents, fileName); +}; + +/** + * Convenience function for waiting for a particular file uploaded + * and a particular item value to be available before continuing. + * @param supertest The super test agent + * @param fileName The filename imported + * @param itemValue The item value to wait for + */ +export const waitForListItem = async ( + supertest: SuperTest, + itemValue: string, + fileName: string +): Promise => { + await waitFor(async () => { + const { status } = await supertest + .get(`${LIST_ITEM_URL}?list_id=${fileName}&value=${itemValue}`) + .send(); + + return status === 200; + }, `waitForListItem fileName: "${fileName}" itemValue: "${itemValue}"`); +}; + +/** + * Convenience function for waiting for a particular file uploaded + * and particular item values to be available before continuing. + * @param supertest The super test agent + * @param fileName The filename imported + * @param itemValue The item value to wait for + */ +export const waitForListItems = async ( + supertest: SuperTest, + itemValues: string[], + fileName: string +): Promise => { + await Promise.all(itemValues.map((item) => waitForListItem(supertest, item, fileName))); +}; + +/** + * Convenience function for waiting for a particular file uploaded + * and a particular item value to be available before continuing. + * @param supertest The super test agent + * @param fileName The filename imported + * @param itemValue The item value to wait for + */ +export const waitForTextListItem = async ( + supertest: SuperTest, + itemValue: string, + fileName: string +): Promise => { + const tokens = itemValue.split(' '); + await waitFor(async () => { + const promises = await Promise.all( + tokens.map(async (token) => { + const { status } = await supertest + .get(`${LIST_ITEM_URL}?list_id=${fileName}&value=${token}`) + .send(); + return status === 200; + }) + ); + return promises.every((one) => one); + }, `waitForTextListItem fileName: "${fileName}" itemValue: "${itemValue}"`); +}; + +/** + * Convenience function for waiting for a particular file uploaded + * and particular item values to be available before continuing. This works + * specifically with text types and does tokenization to ensure all words are uploaded + * @param supertest The super test agent + * @param fileName The filename imported + * @param itemValue The item value to wait for + */ +export const waitForTextListItems = async ( + supertest: SuperTest, + itemValues: string[], + fileName: string +): Promise => { + await Promise.all(itemValues.map((item) => waitForTextListItem(supertest, item, fileName))); +};