Skip to content

Commit be88eac

Browse files
authored
[Automatic Migrations] E2E Qradar Tests (elastic#257392)
## Summary - Fixes elastic/security-team#14953 Adds e2e Cypress tests for QRadar. ### Flaky Test Runner Build : https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/11090#019ce790-3a15-4ff4-9b28-de57e24dcafd ## Flaky Test Runner Stats ### 🎉 All tests passed! - [kibana-flaky-test-suite-runner#11090](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/11090) [✅] Security Solution Investigations - Cypress: 50/50 tests passed. [✅] [Serverless] Security Solution Investigations - Cypress: 50/50 tests passed. [see run history](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds?branch=refs%2Fpull%2F257392%2Fhead)
1 parent 5663f32 commit be88eac

9 files changed

Lines changed: 583 additions & 4 deletions

File tree

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2656,6 +2656,7 @@ x-pack/solutions/security/plugins/entity_store @elastic/core-analysis
26562656

26572657
## Security Solution sub teams - Threat Hunting
26582658

2659+
/x-pack/solutions/security/test/security_solution_cypress/cypress/fixtures/siem_migrations @elastic/security-threat-hunting
26592660
/x-pack/solutions/security/plugins/security_solution/common/siem_migrations @elastic/security-threat-hunting
26602661
/x-pack/solutions/security/plugins/security_solution/public/common/components/control_columns @elastic/security-threat-hunting
26612662
/x-pack/solutions/security/plugins/security_solution/public/siem_migrations @elastic/security-threat-hunting

x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/migration_source_step/use_migration_source_options.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const useMigrationSourceOptions = () => {
1818
{
1919
value: MigrationSource.SPLUNK,
2020
inputDisplay: <span>{MIGRATION_VENDOR_DISPLAY_NAME[MigrationSource.SPLUNK]}</span>,
21+
'data-test-subj': `migrationSourceOption-${MigrationSource.SPLUNK}`,
2122
},
2223
];
2324

@@ -27,9 +28,10 @@ export const useMigrationSourceOptions = () => {
2728
inputDisplay: (
2829
<span>
2930
{MIGRATION_VENDOR_DISPLAY_NAME[MigrationSource.QRADAR]}
30-
<EuiIcon type="flask" />
31+
<EuiIcon type="flask" aria-label="Technical Preview" />
3132
</span>
3233
),
34+
'data-test-subj': `migrationSourceOption-${MigrationSource.QRADAR}`,
3335
});
3436
}
3537
return options;

x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/migration_status_panels/migration_result_panel.test.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,18 @@ describe('RuleMigrationResultPanel', () => {
124124
await waitFor(() => expect(screen.getByTestId('translatedResultsTable')).toBeInTheDocument());
125125
});
126126

127+
it('renders correct translation status counts', async () => {
128+
renderTestComponent();
129+
await waitFor(() => expect(screen.getByTestId('translatedResultsTable')).toBeInTheDocument());
130+
131+
expect(screen.getByTestId('translationStatusCount-Translated')).toHaveTextContent('1');
132+
expect(screen.getByTestId('translationStatusCount-Partially translated')).toHaveTextContent(
133+
'2'
134+
);
135+
expect(screen.getByTestId('translationStatusCount-Not translated')).toHaveTextContent('3');
136+
expect(screen.getByTestId('translationStatusCount-Failed')).toHaveTextContent('4');
137+
});
138+
127139
it('renders upload missing panel', () => {
128140
mockGetMissingResources.mockReturnValue([{}]);
129141
renderTestComponent();

x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/index.test.tsx

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*/
77

88
import React from 'react';
9-
import { render, fireEvent } from '@testing-library/react';
9+
import { render, fireEvent, waitFor } from '@testing-library/react';
1010
import { TestProviders } from '../../../../common/mock/test_providers';
1111
import { MigrationRuleDetailsFlyout } from '.';
1212
import { getRuleMigrationRuleMock } from '../../../../../common/siem_migrations/model/__mocks__';
@@ -130,4 +130,53 @@ describe('MigrationRuleDetailsFlyout', () => {
130130
expect(getByTestId('detailsFlyoutCloseButton')).toBeInTheDocument();
131131
expect(getByTestId('detailsFlyoutCloseButton')).toHaveTextContent('Close');
132132
});
133+
134+
it('displays MITRE ATT&CK mappings in the overview tab', async () => {
135+
const ruleWithThreat = getRuleMigrationRuleMock({
136+
elastic_rule: {
137+
severity: 'low',
138+
risk_score: 21,
139+
query: 'FROM logs-* | WHERE event.category == "authentication"',
140+
description: 'Test rule for detecting successful authentication events',
141+
query_language: 'esql',
142+
title: 'QRadar Test Rule - Authentication Success',
143+
threat: [
144+
{
145+
framework: 'MITRE ATT&CK',
146+
tactic: {
147+
id: 'TA0001',
148+
name: 'Initial Access',
149+
reference: 'https://attack.mitre.org/tactics/TA0001',
150+
},
151+
technique: [
152+
{
153+
id: 'T1078',
154+
name: 'Valid Accounts',
155+
reference: 'https://attack.mitre.org/techniques/T1078',
156+
subtechnique: [],
157+
},
158+
],
159+
},
160+
],
161+
},
162+
});
163+
164+
const { getByTestId } = render(
165+
<TestProviders>
166+
<MigrationRuleDetailsFlyout migrationRule={ruleWithThreat} closeFlyout={closeFlyout} />
167+
</TestProviders>
168+
);
169+
170+
fireEvent.click(getByTestId('tabOverview'));
171+
172+
await waitFor(() => {
173+
expect(getByTestId('threatPropertyTitle')).toBeInTheDocument();
174+
});
175+
await waitFor(() => {
176+
expect(getByTestId('threatTacticLink')).toHaveTextContent(/Initial Access/);
177+
});
178+
await waitFor(() => {
179+
expect(getByTestId('threatTechniqueLink')).toHaveTextContent(/Valid Accounts/);
180+
});
181+
});
133182
});

x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/rules_table/index.test.tsx

Lines changed: 174 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*/
77

88
import React from 'react';
9-
import { render } from '@testing-library/react';
9+
import { render, screen } from '@testing-library/react';
1010
import { MigrationRulesTable } from '.';
1111
import { TestProviders } from '../../../../common/mock';
1212
import { useKibana } from '../../../../common/lib/kibana';
@@ -27,6 +27,10 @@ import { useMigrationRuleDetailsFlyout } from '../../hooks/use_migration_rule_pr
2727
import { useStartRulesMigrationModal } from '../../hooks/use_start_rules_migration_modal';
2828
import { useMigrationRulesTableColumns } from '../../hooks/use_migration_rules_table_columns';
2929
import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock';
30+
import type { RuleMigrationRule } from '../../../../../common/siem_migrations/model/rule_migration.gen';
31+
import { SiemMigrationStatus } from '../../../../../common/siem_migrations/constants';
32+
import type { TableColumn } from '../rules_table_columns/constants';
33+
import { MigrationSource } from '../../../common/types';
3034

3135
jest.mock('../../../../common/lib/kibana');
3236
jest.mock('../../../../common/hooks/use_app_toasts');
@@ -45,6 +49,104 @@ const mockRule = migrationRules[0];
4549
const mockMigrationStats = getRuleMigrationStatsMock();
4650
const mockTranslationStats = getRuleMigrationTranslationStatsMock();
4751

52+
const rules: RuleMigrationRule[] = [
53+
{
54+
id: 'qradar-1',
55+
migration_id: 'qradar-migration-001',
56+
original_rule: {
57+
id: 'qradar-rule-100001',
58+
vendor: 'qradar',
59+
title: 'Authentication Success',
60+
description: 'Detects successful authentication',
61+
query: 'event category authentication',
62+
query_language: 'aql',
63+
},
64+
'@timestamp': '2025-05-06T07:53:48.805Z',
65+
status: SiemMigrationStatus.COMPLETED,
66+
created_by: 'test-user',
67+
updated_by: 'test-user',
68+
updated_at: '2025-05-06T07:57:24.929Z',
69+
translation_result: 'full',
70+
elastic_rule: {
71+
severity: 'low',
72+
risk_score: 21,
73+
query: 'FROM logs-*',
74+
description: 'Detects successful authentication',
75+
query_language: 'esql',
76+
title: 'Authentication Success',
77+
},
78+
},
79+
{
80+
id: 'qradar-2',
81+
migration_id: 'qradar-migration-001',
82+
original_rule: {
83+
id: 'qradar-rule-100002',
84+
vendor: 'qradar',
85+
title: 'Network Traffic Anomaly',
86+
description: 'Detects network anomalies',
87+
query: 'event category network',
88+
query_language: 'aql',
89+
},
90+
'@timestamp': '2025-05-06T07:53:48.805Z',
91+
status: SiemMigrationStatus.COMPLETED,
92+
created_by: 'test-user',
93+
updated_by: 'test-user',
94+
updated_at: '2025-05-06T07:57:27.998Z',
95+
translation_result: 'partial',
96+
elastic_rule: {
97+
severity: 'medium',
98+
risk_score: 47,
99+
query: 'FROM logs-*',
100+
description: 'Detects network anomalies',
101+
query_language: 'esql',
102+
title: 'Network Traffic Anomaly',
103+
},
104+
},
105+
{
106+
id: 'qradar-3',
107+
migration_id: 'qradar-migration-001',
108+
original_rule: {
109+
id: 'qradar-rule-100003',
110+
vendor: 'qradar',
111+
title: 'Malware Detection',
112+
description: 'Detects malware activity',
113+
query: 'event category malware',
114+
query_language: 'aql',
115+
},
116+
'@timestamp': '2025-05-06T07:53:48.805Z',
117+
status: SiemMigrationStatus.COMPLETED,
118+
created_by: 'test-user',
119+
updated_by: 'test-user',
120+
updated_at: '2025-05-06T07:57:32.348Z',
121+
translation_result: 'partial',
122+
elastic_rule: {
123+
severity: 'high',
124+
risk_score: 73,
125+
query: 'FROM logs-*',
126+
description: 'Detects malware activity',
127+
query_language: 'esql',
128+
title: 'Malware Detection',
129+
},
130+
},
131+
{
132+
id: 'qradar-4',
133+
migration_id: 'qradar-migration-001',
134+
original_rule: {
135+
id: 'qradar-rule-100004',
136+
vendor: 'qradar',
137+
title: 'Failed Translation Rule',
138+
description: 'Rule that failed to translate',
139+
query: 'complex unsupported query',
140+
query_language: 'aql',
141+
},
142+
'@timestamp': '2025-05-06T07:53:48.805Z',
143+
status: SiemMigrationStatus.FAILED,
144+
created_by: 'test-user',
145+
updated_by: 'test-user',
146+
updated_at: '2025-05-06T07:57:33.042Z',
147+
},
148+
];
149+
48150
describe('MigrationRulesTable', () => {
49151
let appToastsMock: jest.Mocked<ReturnType<typeof useAppToastsMock.create>>;
50152

@@ -129,4 +231,75 @@ describe('MigrationRulesTable', () => {
129231

130232
expect(getByTestId('siemMigrationsRulesTable')).toBeInTheDocument();
131233
});
234+
235+
describe('Table results', () => {
236+
const statusColumns: TableColumn[] = [
237+
{
238+
field: 'elastic_rule.title',
239+
name: 'Rule',
240+
render: (_: unknown, rule: RuleMigrationRule) => (
241+
<span>{rule.elastic_rule?.title ?? rule.original_rule.title}</span>
242+
),
243+
},
244+
{
245+
field: 'translation_result',
246+
name: 'Status',
247+
render: (_: unknown, rule: RuleMigrationRule) => (
248+
<span data-test-subj={`translationStatus-${rule.translation_result ?? rule.status}`}>
249+
{rule.translation_result ?? rule.status}
250+
</span>
251+
),
252+
},
253+
];
254+
255+
beforeEach(() => {
256+
(useGetMigrationRules as jest.Mock).mockReturnValue({
257+
data: { migrationRules: rules, total: rules.length },
258+
isLoading: false,
259+
});
260+
(useGetMigrationTranslationStats as jest.Mock).mockReturnValue({
261+
data: getRuleMigrationTranslationStatsMock({
262+
rules: {
263+
total: 4,
264+
success: {
265+
total: 3,
266+
result: { full: 1, partial: 2, untranslatable: 0 },
267+
installable: 1,
268+
prebuilt: 0,
269+
missing_index: 0,
270+
},
271+
failed: 1,
272+
},
273+
}),
274+
isLoading: false,
275+
});
276+
(useMigrationRulesTableColumns as jest.Mock).mockReturnValue(statusColumns);
277+
});
278+
279+
test('should render correct number of QRadar migration rule rows', () => {
280+
render(
281+
<MigrationRulesTable
282+
migrationStats={getRuleMigrationStatsMock({ vendor: MigrationSource.QRADAR })}
283+
/>,
284+
{ wrapper: TestProviders }
285+
);
286+
287+
expect(screen.getByTestId('rules-translation-table')).toBeInTheDocument();
288+
const rows = screen.getByTestId('rules-translation-table').querySelectorAll('.euiTableRow');
289+
expect(rows).toHaveLength(4);
290+
});
291+
292+
test('should render correct translation status for each QRadar rule', () => {
293+
render(
294+
<MigrationRulesTable
295+
migrationStats={getRuleMigrationStatsMock({ vendor: MigrationSource.QRADAR })}
296+
/>,
297+
{ wrapper: TestProviders }
298+
);
299+
300+
expect(screen.getAllByTestId('translationStatus-partial')).toHaveLength(2);
301+
expect(screen.getAllByTestId('translationStatus-full')).toHaveLength(1);
302+
expect(screen.getAllByTestId('translationStatus-failed')).toHaveLength(1);
303+
});
304+
});
132305
});

0 commit comments

Comments
 (0)