Skip to content

Commit 12784c2

Browse files
[Security Solution][Entity Store] Add a Host Transform integration test case and refactor them (#220560)
## Summary Towards elastic/security-team#12346 - Add a second test which checks all the fields covered by Host transform - Refactor existing test, extracting common parts, preparing ground for a suite of smaller test ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [x] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [x] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: kibanamachine <[email protected]>
1 parent 0ba35cb commit 12784c2

File tree

1 file changed

+152
-38
lines changed
  • x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier

1 file changed

+152
-38
lines changed

x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/host_transform.ts

Lines changed: 152 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* 2.0.
66
*/
77
import expect from '@kbn/expect';
8-
import type { Ecs, EcsHost } from '@elastic/ecs';
8+
import { Ecs, EcsHost } from '@elastic/ecs';
99
import type {
1010
IndexRequest,
1111
SearchHit,
@@ -28,7 +28,7 @@ export default function (providerContext: FtrProviderContext) {
2828
const dataView = dataViewRouteHelpersFactory(supertest);
2929
const utils = EntityStoreUtils(providerContext.getService);
3030

31-
describe('Host transform logic', () => {
31+
describe('@ess Host transform logic', () => {
3232
describe('Entity Store is not installed by default', () => {
3333
it("Should return 200 and status 'not_installed'", async () => {
3434
const { body } = await supertest.get('/api/entity_store/status').expect(200);
@@ -46,7 +46,14 @@ export default function (providerContext: FtrProviderContext) {
4646
await dataView.create('security-solution');
4747
// Create a test index matching transform's pattern to store test documents
4848
await es.indices.createDataStream({ name: DATASTREAM_NAME });
49+
});
50+
51+
after(async () => {
52+
await es.indices.deleteDataStream({ name: DATASTREAM_NAME });
53+
await dataView.delete('security-solution');
54+
});
4955

56+
beforeEach(async () => {
5057
// Now we can enable the Entity Store...
5158
const response = await supertest
5259
.post('/api/entity_store/enable')
@@ -66,11 +73,6 @@ export default function (providerContext: FtrProviderContext) {
6673
});
6774
});
6875

69-
after(async () => {
70-
await es.indices.deleteDataStream({ name: DATASTREAM_NAME });
71-
await dataView.delete('security-solution');
72-
});
73-
7476
afterEach(async () => {
7577
await utils.cleanEngines();
7678
});
@@ -96,37 +98,9 @@ export default function (providerContext: FtrProviderContext) {
9698

9799
it('Should successfully trigger a host transform', async () => {
98100
const HOST_NAME: string = 'host-transform-test-ip';
99-
const IPs: string[] = ['1.1.1.1', '2.2.2.2'];
100-
const { count, transforms } = await es.transform.getTransformStats({
101-
transform_id: HOST_TRANSFORM_ID,
102-
});
103-
expect(count).to.eql(1);
104-
let transform = transforms[0];
105-
expect(transform.id).to.eql(HOST_TRANSFORM_ID);
106-
const triggerCount: number = transform.stats.trigger_count;
107-
const docsProcessed: number = transform.stats.documents_processed;
108-
109-
// Create two documents with the same host.name, different IPs
110-
for (const ip of IPs) {
111-
const { result } = await es.index(buildHostTransformDocument(HOST_NAME, { ip }));
112-
expect(result).to.eql('created');
113-
}
101+
const testDocs: EcsHost[] = [{ ip: '1.1.1.1' }, { ip: '2.2.2.2' }];
114102

115-
// Trigger the transform manually
116-
const { acknowledged } = await es.transform.scheduleNowTransform({
117-
transform_id: HOST_TRANSFORM_ID,
118-
});
119-
expect(acknowledged).to.be(true);
120-
121-
await retry.waitForWithTimeout('Transform to run again', TIMEOUT_MS, async () => {
122-
const response = await es.transform.getTransformStats({
123-
transform_id: HOST_TRANSFORM_ID,
124-
});
125-
transform = response.transforms[0];
126-
expect(transform.stats.trigger_count).to.greaterThan(triggerCount);
127-
expect(transform.stats.documents_processed).to.greaterThan(docsProcessed);
128-
return true;
129-
});
103+
await createDocumentsAndTriggerTransform(providerContext, HOST_NAME, testDocs);
130104

131105
await retry.waitForWithTimeout(
132106
'Document to be processed and transformed',
@@ -145,7 +119,75 @@ export default function (providerContext: FtrProviderContext) {
145119
const hit = result.hits.hits[0] as SearchHit<Ecs>;
146120
expect(hit._source).ok();
147121
expect(hit._source?.host?.name).to.eql(HOST_NAME);
148-
expect(hit._source?.host?.ip).to.eql(IPs);
122+
expect(hit._source?.host?.ip).to.eql(['1.1.1.1', '2.2.2.2']);
123+
124+
return true;
125+
}
126+
);
127+
});
128+
129+
it('Should successfully collect all expected fields', async () => {
130+
const HOST_NAME: string = 'host-transform-test-all-fields';
131+
const testDocs: EcsHost[] = [
132+
{
133+
domain: 'example.com',
134+
hostname: 'example.com',
135+
id: 'alpha',
136+
os: {
137+
name: 'ubuntu',
138+
type: 'linux',
139+
},
140+
mac: 'abc',
141+
architecture: 'x86-64',
142+
type: 'machineA',
143+
ip: '1.1.1.1',
144+
},
145+
{
146+
domain: 'example.com',
147+
hostname: 'sub.example.com',
148+
id: 'beta',
149+
os: {
150+
name: 'macos',
151+
type: 'darwin',
152+
},
153+
mac: 'def',
154+
architecture: 'arm64',
155+
type: 'machineB',
156+
ip: '2.2.2.2',
157+
},
158+
];
159+
160+
await createDocumentsAndTriggerTransform(providerContext, HOST_NAME, testDocs);
161+
162+
await retry.waitForWithTimeout(
163+
'Document to be processed and transformed',
164+
TIMEOUT_MS,
165+
async () => {
166+
const result = await es.search({
167+
index: INDEX_NAME,
168+
query: {
169+
term: {
170+
'host.name': HOST_NAME,
171+
},
172+
},
173+
});
174+
const total = result.hits.total as SearchTotalHits;
175+
expect(total.value).to.eql(1);
176+
expect(result.hits.hits[0]._source).ok();
177+
const source = result.hits.hits[0]._source as HostTransformResult;
178+
expect(source.host).ok();
179+
const hit = source.host as HostTransformResultHost;
180+
181+
expect(hit.name).to.eql(HOST_NAME);
182+
expectFieldToEqualValues(hit.domain, ['example.com']);
183+
expectFieldToEqualValues(hit.hostname, ['example.com', 'sub.example.com']);
184+
expectFieldToEqualValues(hit.id, ['alpha', 'beta']);
185+
expectFieldToEqualValues(hit.os?.name, ['ubuntu', 'macos']);
186+
expectFieldToEqualValues(hit.os?.type, ['linux', 'darwin']);
187+
expectFieldToEqualValues(hit.ip, ['1.1.1.1', '2.2.2.2']);
188+
expectFieldToEqualValues(hit.mac, ['abc', 'def']);
189+
expectFieldToEqualValues(hit.type, ['machineA', 'machineB']);
190+
expectFieldToEqualValues(hit.architecture, ['x86-64', 'arm64']);
149191

150192
return true;
151193
}
@@ -155,6 +197,20 @@ export default function (providerContext: FtrProviderContext) {
155197
});
156198
}
157199

200+
function expectFieldToEqualValues(field: string[] | undefined, values: string[] | undefined) {
201+
if (values === undefined) {
202+
expect(field).to.not.be(undefined);
203+
}
204+
expect(field).to.ok();
205+
const definedValues = values as string[];
206+
expect((field as string[]).length).to.eql(definedValues.length);
207+
const sortedField: string[] = (field as string[]).sort((a, b) => (a > b ? 1 : -1));
208+
const sortedValues: string[] = definedValues.sort((a, b) => (a > b ? 1 : -1));
209+
for (let i = 0; i < sortedField.length; i++) {
210+
expect(sortedField[i]).to.eql(sortedValues[i]);
211+
}
212+
}
213+
158214
function buildHostTransformDocument(name: string, host: EcsHost): IndexRequest {
159215
host.name = name;
160216
// Get timestamp without the millisecond part
@@ -168,3 +224,61 @@ function buildHostTransformDocument(name: string, host: EcsHost): IndexRequest {
168224
};
169225
return document;
170226
}
227+
228+
async function createDocumentsAndTriggerTransform(
229+
providerContext: FtrProviderContext,
230+
documentName: string,
231+
docs: EcsHost[]
232+
): Promise<void> {
233+
const retry = providerContext.getService('retry');
234+
const es = providerContext.getService('es');
235+
236+
const { count, transforms } = await es.transform.getTransformStats({
237+
transform_id: HOST_TRANSFORM_ID,
238+
});
239+
expect(count).to.eql(1);
240+
let transform = transforms[0];
241+
expect(transform.id).to.eql(HOST_TRANSFORM_ID);
242+
const triggerCount: number = transform.stats.trigger_count;
243+
const docsProcessed: number = transform.stats.documents_processed;
244+
245+
for (let i = 0; i < docs.length; i++) {
246+
const { result } = await es.index(buildHostTransformDocument(documentName, docs[i]));
247+
expect(result).to.eql('created');
248+
}
249+
250+
// Trigger the transform manually
251+
const { acknowledged } = await es.transform.scheduleNowTransform({
252+
transform_id: HOST_TRANSFORM_ID,
253+
});
254+
expect(acknowledged).to.be(true);
255+
256+
await retry.waitForWithTimeout('Transform to run again', TIMEOUT_MS, async () => {
257+
const response = await es.transform.getTransformStats({
258+
transform_id: HOST_TRANSFORM_ID,
259+
});
260+
transform = response.transforms[0];
261+
expect(transform.stats.trigger_count).to.greaterThan(triggerCount);
262+
expect(transform.stats.documents_processed).to.greaterThan(docsProcessed);
263+
return true;
264+
});
265+
}
266+
267+
interface HostTransformResult {
268+
host: HostTransformResultHost;
269+
}
270+
271+
interface HostTransformResultHost {
272+
name: string;
273+
domain: string[] | undefined;
274+
hostname: string[] | undefined;
275+
id: string[] | undefined;
276+
os: {
277+
name: string[] | undefined;
278+
type: string[] | undefined;
279+
};
280+
mac: string[] | undefined;
281+
architecture: string[] | undefined;
282+
type: string[] | undefined;
283+
ip: string[] | undefined;
284+
}

0 commit comments

Comments
 (0)