Skip to content

Commit

Permalink
[Rules migration][Integration test] Update migration API (elastic#11232
Browse files Browse the repository at this point in the history
…) (elastic#211196)

## Summary

[Internal link](elastic/security-team#10820)
to the feature details

Part of elastic/security-team#11232

This PR covers SIEM Migrations Update API (route: `PUT
/internal/siem_migrations/rules/{migration_id}`) integration test:
* Happy path
  * update migration
  * ignore attributes that are not eligible for update
* Error handling
  * an empty content response
  * an error when rule's id is not specified
  * an error when undefined payload has been passed

Also, as part of this PR, I added error handling cases for Create API:
* no content error
* an error when undefined payload has been passed
* an error when original rule id is not specified
* error when original rule vendor is not specified
* an error when original rule title is not specified
* an error when original rule description is not specified
* an error when original rule query is not specified
* an error when original rule query_language is not specified

---------

Co-authored-by: Sergi Massaneda <sergi.massaneda@gmail.com>
  • Loading branch information
e40pud and semd authored Feb 14, 2025
1 parent 20aac5c commit 819fd7a
Show file tree
Hide file tree
Showing 5 changed files with 350 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ export const registerSiemRuleMigrationsUpdateRoute = (
const { migration_id: migrationId } = req.params;
const rulesToUpdate = req.body;

if (rulesToUpdate.length === 0) {
return res.noContent();
}
const ids = rulesToUpdate.map((rule) => rule.id);

const siemMigrationAuditLogger = new SiemMigrationAuditLogger(context.securitySolution);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,78 +28,171 @@ export default ({ getService }: FtrProviderContext) => {
await deleteAllMigrationRules(es);
});

it('should create migrations with provided id', async () => {
const migrationId = uuidv4();
await migrationRulesRoutes.create({ migrationId, body: [defaultOriginalRule] });

// fetch migration rule
const response = await migrationRulesRoutes.get({ migrationId });
expect(response.body.total).toEqual(1);

const migrationRule = response.body.data[0];
expect(migrationRule).toEqual(
expect.objectContaining({
migration_id: migrationId,
original_rule: defaultOriginalRule,
status: SiemMigrationStatus.PENDING,
})
);
});
describe('Happy path', () => {
it('should create migrations with provided id', async () => {
const migrationId = uuidv4();
await migrationRulesRoutes.create({ migrationId, payload: [defaultOriginalRule] });

it('should create migrations without provided id', async () => {
const {
body: { migration_id: migrationId },
} = await migrationRulesRoutes.create({ body: [defaultOriginalRule] });

// fetch migration rule
const response = await migrationRulesRoutes.get({ migrationId });
expect(response.body.total).toEqual(1);

const migrationRule = response.body.data[0];
expect(migrationRule).toEqual(
expect.objectContaining({
migration_id: migrationId,
original_rule: defaultOriginalRule,
status: SiemMigrationStatus.PENDING,
})
);
});
// fetch migration rule
const response = await migrationRulesRoutes.get({ migrationId });
expect(response.body.total).toEqual(1);

const migrationRule = response.body.data[0];
expect(migrationRule).toEqual(
expect.objectContaining({
migration_id: migrationId,
original_rule: defaultOriginalRule,
status: SiemMigrationStatus.PENDING,
})
);
});

it('should create migrations without provided id', async () => {
const {
body: { migration_id: migrationId },
} = await migrationRulesRoutes.create({ payload: [defaultOriginalRule] });

// fetch migration rule
const response = await migrationRulesRoutes.get({ migrationId });
expect(response.body.total).toEqual(1);

const migrationRule = response.body.data[0];
expect(migrationRule).toEqual(
expect.objectContaining({
migration_id: migrationId,
original_rule: defaultOriginalRule,
status: SiemMigrationStatus.PENDING,
})
);
});

it('should create migrations with the rules that have resources', async () => {
const migrationId = uuidv4();
await migrationRulesRoutes.create({ migrationId, payload: [splunkRuleWithResources] });

// fetch migration rule
const response = await migrationRulesRoutes.get({ migrationId });
expect(response.body.total).toEqual(1);

const migrationRule = response.body.data[0];
expect(migrationRule).toEqual(
expect.objectContaining({
migration_id: migrationId,
original_rule: splunkRuleWithResources,
status: SiemMigrationStatus.PENDING,
})
);

it('should create migrations with the rules that have resources', async () => {
const migrationId = uuidv4();
await migrationRulesRoutes.create({ migrationId, body: [splunkRuleWithResources] });

// fetch migration rule
const response = await migrationRulesRoutes.get({ migrationId });
expect(response.body.total).toEqual(1);

const migrationRule = response.body.data[0];
expect(migrationRule).toEqual(
expect.objectContaining({
migration_id: migrationId,
original_rule: splunkRuleWithResources,
status: SiemMigrationStatus.PENDING,
})
);

// fetch missing resources
const resourcesResponse = await migrationResourcesRoutes.getMissingResources({
migrationId,
// fetch missing resources
const resourcesResponse = await migrationResourcesRoutes.getMissingResources({
migrationId,
});
expect(resourcesResponse.body).toEqual([
{ type: 'macro', name: 'summariesonly' },
{ type: 'macro', name: 'drop_dm_object_name(1)' },
{ type: 'lookup', name: 'malware_tracker' },
]);
});
expect(resourcesResponse.body).toEqual([
{ type: 'macro', name: 'summariesonly' },
{ type: 'macro', name: 'drop_dm_object_name(1)' },
{ type: 'lookup', name: 'malware_tracker' },
]);
});

it('should return no content error', async () => {
const migrationId = uuidv4();
await migrationRulesRoutes.create({ migrationId, body: [], expectStatusCode: 204 });
describe('Error handling', () => {
it('should return no content error', async () => {
const migrationId = uuidv4();
await migrationRulesRoutes.create({ migrationId, payload: [], expectStatusCode: 204 });

// fetch migration rule
const response = await migrationRulesRoutes.get({ migrationId });
expect(response.body.total).toEqual(0);
});

it(`should return an error when undefined payload has been passed`, async () => {
const migrationId = uuidv4();
const response = await migrationRulesRoutes.create({ migrationId, expectStatusCode: 400 });

// fetch migration rule
const response = await migrationRulesRoutes.get({ migrationId });
expect(response.body.total).toEqual(0);
expect(response.body).toEqual({
error: 'Bad Request',
message: '[request body]: Expected array, received null',
statusCode: 400,
});
});

it('should return an error when original rule id is not specified', async () => {
const { id, ...restOfOriginalRule } = defaultOriginalRule;
const response = await migrationRulesRoutes.create({
payload: [restOfOriginalRule],
expectStatusCode: 400,
});
expect(response.body).toEqual({
statusCode: 400,
error: 'Bad Request',
message: '[request body]: 0.id: Required',
});
});

it('should return an error when original rule vendor is not specified', async () => {
const { vendor, ...restOfOriginalRule } = defaultOriginalRule;
const response = await migrationRulesRoutes.create({
payload: [restOfOriginalRule],
expectStatusCode: 400,
});
expect(response.body).toEqual({
statusCode: 400,
error: 'Bad Request',
message: '[request body]: 0.vendor: Invalid literal value, expected "splunk"',
});
});

it('should return an error when original rule title is not specified', async () => {
const { title, ...restOfOriginalRule } = defaultOriginalRule;
const response = await migrationRulesRoutes.create({
payload: [restOfOriginalRule],
expectStatusCode: 400,
});
expect(response.body).toEqual({
statusCode: 400,
error: 'Bad Request',
message: '[request body]: 0.title: Required',
});
});

it('should return an error when original rule description is not specified', async () => {
const { description, ...restOfOriginalRule } = defaultOriginalRule;
const response = await migrationRulesRoutes.create({
payload: [restOfOriginalRule],
expectStatusCode: 400,
});
expect(response.body).toEqual({
statusCode: 400,
error: 'Bad Request',
message: '[request body]: 0.description: Required',
});
});

it('should return an error when original rule query is not specified', async () => {
const { query, ...restOfOriginalRule } = defaultOriginalRule;
const response = await migrationRulesRoutes.create({
payload: [restOfOriginalRule],
expectStatusCode: 400,
});
expect(response.body).toEqual({
statusCode: 400,
error: 'Bad Request',
message: '[request body]: 0.query: Required',
});
});

it('should return an error when original rule query_language is not specified', async () => {
const { query_language: _, ...restOfOriginalRule } = defaultOriginalRule;
const response = await migrationRulesRoutes.create({
payload: [restOfOriginalRule],
expectStatusCode: 400,
});
expect(response.body).toEqual({
statusCode: 400,
error: 'Bad Request',
message: '[request body]: 0.query_language: Required',
});
});
});
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ export default function ({ loadTestFile }: FtrProviderContext) {
describe('@ess SecuritySolution SIEM Migrations', () => {
loadTestFile(require.resolve('./create'));
loadTestFile(require.resolve('./get'));
loadTestFile(require.resolve('./update'));
});
}
Loading

0 comments on commit 819fd7a

Please sign in to comment.