Skip to content

Commit

Permalink
[Security Solution] Extend the /upgrade/_perform API endpoint's con…
Browse files Browse the repository at this point in the history
…tract migrating to Zod (#189790)

Partially addresses (contract change only):
#166376

Created in favour of: #189187
(closed)

## Summary

- Extends contract as described in the
[POC](#144060), migrating from
`io-ts` to Zod (search for `Perform rule upgrade`)
- Uses new types in endpoint, but functionality remains unchaged.

### For maintainers

- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
  • Loading branch information
jpdjere authored Aug 5, 2024
1 parent 986e760 commit 8d550b0
Show file tree
Hide file tree
Showing 4 changed files with 395 additions and 98 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@
* 2.0.
*/

export interface AggregatedPrebuiltRuleError {
message: string;
status_code?: number;
rules: Array<{
rule_id: string;
name?: string;
}>;
}
import { z } from 'zod';
import { RuleName, RuleSignatureId } from '../../model/rule_schema/common_attributes.gen';

export type AggregatedPrebuiltRuleError = z.infer<typeof AggregatedPrebuiltRuleError>;
export const AggregatedPrebuiltRuleError = z.object({
message: z.string(),
status_code: z.number().optional(),
rules: z.array(
z.object({
rule_id: RuleSignatureId,
name: RuleName.optional(),
})
),
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { expectParseError, expectParseSuccess, stringifyZodError } from '@kbn/zod-helpers';
import {
PickVersionValues,
RuleUpgradeSpecifier,
UpgradeSpecificRulesRequest,
UpgradeAllRulesRequest,
PerformRuleUpgradeResponseBody,
PerformRuleUpgradeRequestBody,
} from './perform_rule_upgrade_route';

describe('Perform Rule Upgrade Route Schemas', () => {
describe('PickVersionValues', () => {
test('validates correct enum values', () => {
const validValues = ['BASE', 'CURRENT', 'TARGET', 'MERGED'];
validValues.forEach((value) => {
const result = PickVersionValues.safeParse(value);
expectParseSuccess(result);
expect(result.data).toBe(value);
});
});

test('rejects invalid enum values', () => {
const invalidValues = ['RESOLVED', 'MALFORMED_STRING'];
invalidValues.forEach((value) => {
const result = PickVersionValues.safeParse(value);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"Invalid enum value. Expected 'BASE' | 'CURRENT' | 'TARGET' | 'MERGED', received '${value}'"`
);
});
});
});

describe('RuleUpgradeSpecifier', () => {
const validSpecifier = {
rule_id: 'rule-1',
revision: 1,
version: 1,
pick_version: 'TARGET',
};

test('validates a valid upgrade specifier without fields property', () => {
const result = RuleUpgradeSpecifier.safeParse(validSpecifier);
expectParseSuccess(result);
expect(result.data).toEqual(validSpecifier);
});

test('validates a valid upgrade specifier with a fields property', () => {
const specifierWithFields = {
...validSpecifier,
fields: {
name: {
pick_version: 'CURRENT',
},
},
};
const result = RuleUpgradeSpecifier.safeParse(specifierWithFields);
expectParseSuccess(result);
expect(result.data).toEqual(specifierWithFields);
});

test('rejects upgrade specifier with invalid pick_version rule_id', () => {
const invalid = { ...validSpecifier, rule_id: 123 };
const result = RuleUpgradeSpecifier.safeParse(invalid);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"rule_id: Expected string, received number"`
);
});
});

describe('UpgradeSpecificRulesRequest', () => {
const validRequest = {
mode: 'SPECIFIC_RULES',
rules: [
{
rule_id: 'rule-1',
revision: 1,
version: 1,
},
],
};

test('validates a correct upgrade specific rules request', () => {
const result = UpgradeSpecificRulesRequest.safeParse(validRequest);
expectParseSuccess(result);
expect(result.data).toEqual(validRequest);
});

test('rejects invalid mode', () => {
const invalid = { ...validRequest, mode: 'INVALID_MODE' };
const result = UpgradeSpecificRulesRequest.safeParse(invalid);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"mode: Invalid literal value, expected \\"SPECIFIC_RULES\\""`
);
});

test('rejects paylaod with missing rules array', () => {
const invalid = { ...validRequest, rules: undefined };
const result = UpgradeSpecificRulesRequest.safeParse(invalid);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"rules: Required"`);
});
});

describe('UpgradeAllRulesRequest', () => {
const validRequest = {
mode: 'ALL_RULES',
};

test('validates a correct upgrade all rules request', () => {
const result = UpgradeAllRulesRequest.safeParse(validRequest);
expectParseSuccess(result);
expect(result.data).toEqual(validRequest);
});

test('allows optional pick_version', () => {
const withPickVersion = { ...validRequest, pick_version: 'BASE' };
const result = UpgradeAllRulesRequest.safeParse(withPickVersion);
expectParseSuccess(result);
expect(result.data).toEqual(withPickVersion);
});
});

describe('PerformRuleUpgradeRequestBody', () => {
test('validates a correct upgrade specific rules request', () => {
const validRequest = {
mode: 'SPECIFIC_RULES',
pick_version: 'BASE',
rules: [
{
rule_id: 'rule-1',
revision: 1,
version: 1,
},
],
};
const result = PerformRuleUpgradeRequestBody.safeParse(validRequest);
expectParseSuccess(result);
expect(result.data).toEqual(validRequest);
});

test('validates a correct upgrade all rules request', () => {
const validRequest = {
mode: 'ALL_RULES',
pick_version: 'BASE',
};
const result = PerformRuleUpgradeRequestBody.safeParse(validRequest);
expectParseSuccess(result);
expect(result.data).toEqual(validRequest);
});

test('rejects invalid mode', () => {
const invalid = { mode: 'INVALID_MODE' };
const result = PerformRuleUpgradeRequestBody.safeParse(invalid);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"mode: Invalid discriminator value. Expected 'ALL_RULES' | 'SPECIFIC_RULES'"`
);
});
});
});

describe('PerformRuleUpgradeResponseBody', () => {
const validResponse = {
summary: {
total: 1,
succeeded: 1,
skipped: 0,
failed: 0,
},
results: {
updated: [],
skipped: [],
},
errors: [],
};

test('validates a correct perform rule upgrade response', () => {
const result = PerformRuleUpgradeResponseBody.safeParse(validResponse);
expectParseSuccess(result);
expect(result.data).toEqual(validResponse);
});

test('rejects missing required fields', () => {
const propsToDelete = Object.keys(validResponse);
propsToDelete.forEach((deletedProp) => {
const invalidResponse = Object.fromEntries(
Object.entries(validResponse).filter(([key]) => key !== deletedProp)
);
const result = PerformRuleUpgradeResponseBody.safeParse(invalidResponse);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"${deletedProp}: Required"`);
});
});
});
Loading

0 comments on commit 8d550b0

Please sign in to comment.