From 70c84d9d27e5cc01a653c181fc5f2c4ce74771ca Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Sun, 17 Sep 2023 15:33:21 +0300 Subject: [PATCH] Remove type from create/update legacy routes --- .../server/routes/legacy/create.test.ts | 299 ++++++++++++++++-- .../alerting/server/routes/legacy/create.ts | 3 +- .../server/routes/legacy/update.test.ts | 219 +++++++++++-- .../alerting/server/routes/legacy/update.ts | 3 +- .../server/routes/lib/rewrite_actions.ts | 10 +- 5 files changed, 481 insertions(+), 53 deletions(-) diff --git a/x-pack/plugins/alerting/server/routes/legacy/create.test.ts b/x-pack/plugins/alerting/server/routes/legacy/create.test.ts index c1d3a2201a663..5bc578df172bc 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/create.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/create.test.ts @@ -12,10 +12,12 @@ import { licenseStateMock } from '../../lib/license_state.mock'; import { verifyApiAccess } from '../../lib/license_api_access'; import { mockHandlerArguments } from '../_mock_handler_arguments'; import { rulesClientMock } from '../../rules_client.mock'; -import { Rule, RuleActionTypes } from '../../../common/rule'; +import { RuleActionTypes } from '../../../common/rule'; import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled'; import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage'; +import { actionsClientMock } from '@kbn/actions-plugin/server/mocks'; +import { omit } from 'lodash'; const rulesClient = rulesClientMock.create(); @@ -35,7 +37,26 @@ describe('createAlertRoute', () => { const createdAt = new Date(); const updatedAt = new Date(); - const mockedAlert = { + const action = { + actionTypeId: 'test', + group: 'default', + id: '2', + params: { + foo: true, + }, + type: RuleActionTypes.DEFAULT, + }; + + const systemAction = { + actionTypeId: 'test-2', + id: 'system_action-id', + params: { + foo: true, + }, + type: RuleActionTypes.SYSTEM, + }; + + const createRequest = { alertTypeId: '1', consumer: 'bar', name: 'abc', @@ -45,7 +66,7 @@ describe('createAlertRoute', () => { bar: true, }, throttle: '30s', - notifyWhen: 'onActionGroupChange', + notifyWhen: 'onActionGroupChange' as const, actions: [ { group: 'default', @@ -53,13 +74,12 @@ describe('createAlertRoute', () => { params: { foo: true, }, - type: RuleActionTypes.DEFAULT, }, ], }; - const createResult: Rule<{ bar: boolean }> = { - ...mockedAlert, + const createResult = { + ...createRequest, enabled: true, muteAll: false, createdBy: '', @@ -67,29 +87,36 @@ describe('createAlertRoute', () => { apiKey: '', apiKeyOwner: '', mutedInstanceIds: [], - notifyWhen: 'onActionGroupChange', + notifyWhen: 'onActionGroupChange' as const, createdAt, updatedAt, id: '123', actions: [ { - ...mockedAlert.actions[0], + ...createRequest.actions[0], actionTypeId: 'test', }, ], executionStatus: { - status: 'unknown', + status: 'unknown' as const, lastExecutionDate: new Date('2020-08-20T19:23:38Z'), }, revision: 0, }; + const mockedResponse = { + ...createResult, + actions: [action], + }; + it('creates an alert with proper parameters', async () => { const licenseState = licenseStateMock.create(); const router = httpServiceMock.createRouter(); const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true }); const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract(); const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test'); + const actionsClient = actionsClientMock.create(); + actionsClient.isSystemAction.mockImplementation((id: string) => id === 'system_action-id'); createAlertRoute({ router, @@ -102,12 +129,12 @@ describe('createAlertRoute', () => { expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id?}"`); - rulesClient.create.mockResolvedValueOnce(createResult); + rulesClient.create.mockResolvedValueOnce(mockedResponse); const [context, req, res] = mockHandlerArguments( - { rulesClient }, + { rulesClient, actionsClient }, { - body: mockedAlert, + body: createRequest, }, ['ok'] ); @@ -127,6 +154,7 @@ describe('createAlertRoute', () => { "params": Object { "foo": true, }, + "type": "default", }, ], "alertTypeId": "1", @@ -166,6 +194,8 @@ describe('createAlertRoute', () => { const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true }); const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract(); const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test'); + const actionsClient = actionsClientMock.create(); + actionsClient.isSystemAction.mockImplementation((id: string) => id === 'system_action-id'); createAlertRoute({ router, @@ -178,13 +208,13 @@ describe('createAlertRoute', () => { expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id?}"`); - rulesClient.create.mockResolvedValueOnce(expectedResult); + rulesClient.create.mockResolvedValueOnce({ ...mockedResponse, id: 'custom-id' }); const [context, req, res] = mockHandlerArguments( - { rulesClient }, + { rulesClient, actionsClient }, { params: { id: 'custom-id' }, - body: mockedAlert, + body: createRequest, }, ['ok'] ); @@ -204,6 +234,7 @@ describe('createAlertRoute', () => { "params": Object { "foo": true, }, + "type": "default", }, ], "alertTypeId": "1", @@ -243,6 +274,8 @@ describe('createAlertRoute', () => { const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true }); const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract(); const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test'); + const actionsClient = actionsClientMock.create(); + actionsClient.isSystemAction.mockImplementation((id: string) => id === 'system_action-id'); createAlertRoute({ router, @@ -255,14 +288,14 @@ describe('createAlertRoute', () => { expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id?}"`); - rulesClient.create.mockResolvedValueOnce(expectedResult); + rulesClient.create.mockResolvedValueOnce({ ...mockedResponse, id: 'custom-id' }); rulesClient.getSpaceId.mockReturnValueOnce('default'); const [context, req, res] = mockHandlerArguments( - { rulesClient }, + { rulesClient, actionsClient }, { params: { id: 'custom-id' }, - body: mockedAlert, + body: createRequest, }, ['ok'] ); @@ -282,6 +315,7 @@ describe('createAlertRoute', () => { "params": Object { "foo": true, }, + "type": "default", }, ], "alertTypeId": "1", @@ -321,6 +355,8 @@ describe('createAlertRoute', () => { const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true }); const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract(); const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test'); + const actionsClient = actionsClientMock.create(); + actionsClient.isSystemAction.mockImplementation((id: string) => id === 'system_action-id'); createAlertRoute({ router, @@ -333,14 +369,14 @@ describe('createAlertRoute', () => { expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id?}"`); - rulesClient.create.mockResolvedValueOnce(expectedResult); + rulesClient.create.mockResolvedValueOnce({ ...mockedResponse, id: 'custom-id' }); rulesClient.getSpaceId.mockReturnValueOnce('another-space'); const [context, req, res] = mockHandlerArguments( - { rulesClient }, + { rulesClient, actionsClient }, { params: { id: 'custom-id' }, - body: mockedAlert, + body: createRequest, }, ['ok'] ); @@ -360,6 +396,7 @@ describe('createAlertRoute', () => { "params": Object { "foo": true, }, + "type": "default", }, ], "alertTypeId": "1", @@ -393,14 +430,22 @@ describe('createAlertRoute', () => { const licenseState = licenseStateMock.create(); const router = httpServiceMock.createRouter(); const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true }); + const actionsClient = actionsClientMock.create(); + actionsClient.isSystemAction.mockImplementation((id: string) => id === 'system_action-id'); createAlertRoute({ router, licenseState, encryptedSavedObjects }); const [, handler] = router.post.mock.calls[0]; - rulesClient.create.mockResolvedValueOnce(createResult); + rulesClient.create.mockResolvedValueOnce(mockedResponse); - const [context, req, res] = mockHandlerArguments({ rulesClient }, {}); + const [context, req, res] = mockHandlerArguments( + { rulesClient, actionsClient }, + { + params: { id: 'custom-id' }, + body: createRequest, + } + ); await handler(context, req, res); @@ -411,6 +456,8 @@ describe('createAlertRoute', () => { const licenseState = licenseStateMock.create(); const router = httpServiceMock.createRouter(); const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true }); + const actionsClient = actionsClientMock.create(); + actionsClient.isSystemAction.mockImplementation((id: string) => id === 'system_action-id'); (verifyApiAccess as jest.Mock).mockImplementation(() => { throw new Error('OMG'); @@ -420,9 +467,15 @@ describe('createAlertRoute', () => { const [, handler] = router.post.mock.calls[0]; - rulesClient.create.mockResolvedValueOnce(createResult); + rulesClient.create.mockResolvedValueOnce(mockedResponse); - const [context, req, res] = mockHandlerArguments({ rulesClient }, {}); + const [context, req, res] = mockHandlerArguments( + { rulesClient, actionsClient }, + { + params: { id: 'custom-id' }, + body: createRequest, + } + ); expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); @@ -433,6 +486,8 @@ describe('createAlertRoute', () => { const licenseState = licenseStateMock.create(); const router = httpServiceMock.createRouter(); const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true }); + const actionsClient = actionsClientMock.create(); + actionsClient.isSystemAction.mockImplementation((id: string) => id === 'system_action-id'); createAlertRoute({ router, licenseState, encryptedSavedObjects }); @@ -440,7 +495,14 @@ describe('createAlertRoute', () => { rulesClient.create.mockRejectedValue(new RuleTypeDisabledError('Fail', 'license_invalid')); - const [context, req, res] = mockHandlerArguments({ rulesClient }, {}, ['ok', 'forbidden']); + const [context, req, res] = mockHandlerArguments( + { rulesClient, actionsClient }, + { + params: { id: 'custom-id' }, + body: createRequest, + }, + ['ok', 'forbidden'] + ); await handler(context, req, res); @@ -453,6 +515,8 @@ describe('createAlertRoute', () => { const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true }); const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract(); const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test'); + const actionsClient = actionsClientMock.create(); + actionsClient.isSystemAction.mockImplementation((id: string) => id === 'system_action-id'); createAlertRoute({ router, @@ -460,9 +524,190 @@ describe('createAlertRoute', () => { encryptedSavedObjects, usageCounter: mockUsageCounter, }); + const [, handler] = router.post.mock.calls[0]; - const [context, req, res] = mockHandlerArguments({ rulesClient }, {}, ['ok']); + + rulesClient.create.mockResolvedValueOnce(mockedResponse); + + const [context, req, res] = mockHandlerArguments( + { rulesClient, actionsClient }, + { + params: { id: 'custom-id' }, + body: createRequest, + }, + ['ok'] + ); + await handler(context, req, res); expect(trackLegacyRouteUsage).toHaveBeenCalledWith('create', mockUsageCounter); }); + + describe('actions', () => { + it('adds the type of the actions correctly before passing the request to the rules client', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true }); + const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract(); + const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test'); + const actionsClient = actionsClientMock.create(); + actionsClient.isSystemAction.mockImplementation((id: string) => id === 'system_action-id'); + + createAlertRoute({ + router, + licenseState, + encryptedSavedObjects, + usageCounter: mockUsageCounter, + }); + + const [_, handler] = router.post.mock.calls[0]; + rulesClient.create.mockResolvedValueOnce({ + ...mockedResponse, + actions: [action, systemAction], + }); + + const [context, req, res] = mockHandlerArguments( + { rulesClient, actionsClient }, + { + params: { + id: '1', + }, + body: { ...createRequest, actions: [omit(action, 'type'), omit(systemAction, 'type')] }, + }, + ['ok'] + ); + + await handler(context, req, res); + + expect(rulesClient.create.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "data": Object { + "actions": Array [ + Object { + "group": "default", + "id": "2", + "params": Object { + "foo": true, + }, + "type": "default", + }, + Object { + "id": "system_action-id", + "params": Object { + "foo": true, + }, + "type": "system", + }, + ], + "alertTypeId": "1", + "consumer": "bar", + "name": "abc", + "notifyWhen": "onActionGroupChange", + "params": Object { + "bar": true, + }, + "schedule": Object { + "interval": "10s", + }, + "tags": Array [ + "foo", + ], + "throttle": "30s", + }, + "options": Object { + "id": "1", + }, + }, + ] + `); + }); + + it('removes the type from the actions correctly before sending the response', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true }); + const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract(); + const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test'); + const actionsClient = actionsClientMock.create(); + actionsClient.isSystemAction.mockImplementation((id: string) => id === 'system_action-id'); + + createAlertRoute({ + router, + licenseState, + encryptedSavedObjects, + usageCounter: mockUsageCounter, + }); + + const [_, handler] = router.post.mock.calls[0]; + rulesClient.create.mockResolvedValueOnce({ + ...mockedResponse, + actions: [action, systemAction], + }); + + const [context, req, res] = mockHandlerArguments( + { rulesClient, actionsClient }, + { + params: { + id: '1', + }, + body: { ...createRequest, actions: [omit(action, 'type'), omit(systemAction, 'type')] }, + }, + ['ok'] + ); + + const routeRes = await handler(context, req, res); + + // @ts-expect-error: body exists + expect(routeRes.body.actions).toEqual([ + { + actionTypeId: 'test', + group: 'default', + id: '2', + params: { + foo: true, + }, + }, + { + actionTypeId: 'test-2', + id: 'system_action-id', + params: { + foo: true, + }, + }, + ]); + }); + + it('fails if the action contains a type in the request', async () => { + const actionToValidate = { + group: 'default', + id: '2', + params: { + foo: true, + }, + type: RuleActionTypes.DEFAULT, + }; + + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true }); + const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract(); + const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test'); + + createAlertRoute({ + router, + licenseState, + encryptedSavedObjects, + usageCounter: mockUsageCounter, + }); + + const [config, _] = router.post.mock.calls[0]; + + expect(() => + // @ts-expect-error: body exists + config.validate.body.validate({ ...createRequest, actions: [actionToValidate] }) + ).toThrowErrorMatchingInlineSnapshot( + `"[actions.0.type]: definition for this key is missing"` + ); + }); + }); }); diff --git a/x-pack/plugins/alerting/server/routes/legacy/create.ts b/x-pack/plugins/alerting/server/routes/legacy/create.ts index df1936c48f8da..d4c6c5dfe2d60 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/create.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/create.ts @@ -20,6 +20,7 @@ import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled'; import { RouteOptions } from '..'; import { countUsageOfPredefinedIds, rewriteActionsReq } from '../lib'; import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage'; +import { rewriteActionsResLegacy } from '../lib/rewrite_actions'; export const bodySchema = schema.object({ name: schema.string(), @@ -90,7 +91,7 @@ export const createAlertRoute = ({ router, licenseState, usageCounter }: RouteOp }); return res.ok({ - body: alertRes, + body: { ...alertRes, actions: rewriteActionsResLegacy(alertRes.actions) }, }); } catch (e) { if (e instanceof RuleTypeDisabledError) { diff --git a/x-pack/plugins/alerting/server/routes/legacy/update.test.ts b/x-pack/plugins/alerting/server/routes/legacy/update.test.ts index 83295ab142798..07ad475bae041 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/update.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/update.test.ts @@ -13,8 +13,10 @@ import { verifyApiAccess } from '../../lib/license_api_access'; import { mockHandlerArguments } from '../_mock_handler_arguments'; import { rulesClientMock } from '../../rules_client.mock'; import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled'; -import { RuleActionTypes, RuleNotifyWhen } from '../../../common'; +import { RuleActionTypes, RuleNotifyWhen, RuleSystemAction } from '../../../common'; import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage'; +import { actionsClientMock } from '@kbn/actions-plugin/server/mocks'; +import { omit } from 'lodash'; const rulesClient = rulesClientMock.create(); jest.mock('../../lib/license_api_access', () => ({ @@ -30,6 +32,25 @@ beforeEach(() => { }); describe('updateAlertRoute', () => { + const action = { + group: 'default', + id: '2', + actionTypeId: 'test', + params: { + baz: true, + }, + type: RuleActionTypes.DEFAULT, + }; + + const systemAction: RuleSystemAction = { + actionTypeId: 'test-2', + id: 'system_action-id', + params: { + foo: true, + }, + type: RuleActionTypes.SYSTEM, + }; + const mockedResponse = { id: '1', alertTypeId: '1', @@ -40,6 +61,32 @@ describe('updateAlertRoute', () => { }, createdAt: new Date(), updatedAt: new Date(), + actions: [action], + notifyWhen: RuleNotifyWhen.CHANGE, + }; + + const updateRequest = { + throttle: null, + name: 'abc', + tags: ['bar'], + schedule: { interval: '12s' }, + params: { + otherField: false, + }, + actions: [ + { + group: 'default', + id: '2', + params: { + baz: true, + }, + }, + ], + notifyWhen: 'onActionGroupChange', + }; + + const updateResult = { + ...mockedResponse, actions: [ { group: 'default', @@ -48,10 +95,8 @@ describe('updateAlertRoute', () => { params: { baz: true, }, - type: RuleActionTypes.DEFAULT, }, ], - notifyWhen: RuleNotifyWhen.CHANGE, }; it('updates an alert with proper parameters', async () => { @@ -72,30 +117,12 @@ describe('updateAlertRoute', () => { params: { id: '1', }, - body: { - throttle: null, - name: 'abc', - tags: ['bar'], - schedule: { interval: '12s' }, - params: { - otherField: false, - }, - actions: [ - { - group: 'default', - id: '2', - params: { - baz: true, - }, - }, - ], - notifyWhen: 'onActionGroupChange', - }, + body: updateRequest, }, ['ok'] ); - expect(await handler(context, req, res)).toEqual({ body: mockedResponse }); + expect(await handler(context, req, res)).toEqual({ body: updateResult }); expect(rulesClient.update).toHaveBeenCalledTimes(1); expect(rulesClient.update.mock.calls[0]).toMatchInlineSnapshot(` @@ -109,6 +136,7 @@ describe('updateAlertRoute', () => { "params": Object { "baz": true, }, + "type": "default", }, ], "name": "abc", @@ -248,12 +276,157 @@ describe('updateAlertRoute', () => { const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract(); const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test'); + rulesClient.update.mockResolvedValueOnce(mockedResponse); + updateAlertRoute(router, licenseState, mockUsageCounter); + const [, handler] = router.put.mock.calls[0]; const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [ 'ok', ]); + await handler(context, req, res); expect(trackLegacyRouteUsage).toHaveBeenCalledWith('update', mockUsageCounter); }); + + describe('actions', () => { + it('adds the type of the actions correctly before passing the request to the rules client', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + const actionsClient = actionsClientMock.create(); + actionsClient.isSystemAction.mockImplementation((id: string) => id === 'system_action-id'); + + updateAlertRoute(router, licenseState); + + const [_, handler] = router.put.mock.calls[0]; + rulesClient.update.mockResolvedValueOnce({ + ...mockedResponse, + actions: [action, systemAction], + }); + + const [context, req, res] = mockHandlerArguments( + { rulesClient, actionsClient }, + { + params: { + id: '1', + }, + body: { ...updateRequest, actions: [omit(action, 'type'), omit(systemAction, 'type')] }, + }, + ['ok'] + ); + + await handler(context, req, res); + + expect(rulesClient.update.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "data": Object { + "actions": Array [ + Object { + "group": "default", + "id": "2", + "params": Object { + "baz": true, + }, + "type": "default", + }, + Object { + "id": "system_action-id", + "params": Object { + "foo": true, + }, + "type": "system", + }, + ], + "name": "abc", + "notifyWhen": "onActionGroupChange", + "params": Object { + "otherField": false, + }, + "schedule": Object { + "interval": "12s", + }, + "tags": Array [ + "bar", + ], + "throttle": null, + }, + "id": "1", + }, + ] + `); + }); + + it('removes the type from the actions correctly before sending the response', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + const actionsClient = actionsClientMock.create(); + actionsClient.isSystemAction.mockImplementation((id: string) => id === 'system_action-id'); + + updateAlertRoute(router, licenseState); + + const [_, handler] = router.put.mock.calls[0]; + rulesClient.update.mockResolvedValueOnce({ + ...mockedResponse, + actions: [action, systemAction], + }); + + const [context, req, res] = mockHandlerArguments( + { rulesClient, actionsClient }, + { + params: { + id: '1', + }, + body: { ...updateRequest, actions: [omit(action, 'type'), omit(systemAction, 'type')] }, + }, + ['ok'] + ); + + const routeRes = await handler(context, req, res); + + // @ts-expect-error: body exists + expect(routeRes.body.actions).toEqual([ + { + actionTypeId: 'test', + group: 'default', + id: '2', + params: { + baz: true, + }, + }, + { + actionTypeId: 'test-2', + id: 'system_action-id', + params: { + foo: true, + }, + }, + ]); + }); + + it('fails if the action contains a type in the request', async () => { + const actionToValidate = { + group: 'default', + id: '2', + params: { + foo: true, + }, + type: RuleActionTypes.DEFAULT, + }; + + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + updateAlertRoute(router, licenseState); + + const [config, _] = router.put.mock.calls[0]; + + expect(() => + // @ts-expect-error: body exists + config.validate.body.validate({ ...updateRequest, actions: [actionToValidate] }) + ).toThrowErrorMatchingInlineSnapshot( + `"[actions.0.type]: definition for this key is missing"` + ); + }); + }); }); diff --git a/x-pack/plugins/alerting/server/routes/legacy/update.ts b/x-pack/plugins/alerting/server/routes/legacy/update.ts index f1f02d33e04df..677e78924f212 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/update.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/update.ts @@ -20,6 +20,7 @@ import { validateNotifyWhenType, } from '../../../common'; import { rewriteActionsReq } from '../lib'; +import { rewriteActionsResLegacy } from '../lib/rewrite_actions'; const paramSchema = schema.object({ id: schema.string(), @@ -84,7 +85,7 @@ export const updateAlertRoute = ( }, }); return res.ok({ - body: alertRes, + body: { ...alertRes, actions: rewriteActionsResLegacy(alertRes.actions) }, }); } catch (e) { if (e instanceof RuleTypeDisabledError) { diff --git a/x-pack/plugins/alerting/server/routes/lib/rewrite_actions.ts b/x-pack/plugins/alerting/server/routes/lib/rewrite_actions.ts index 8b86f6af97386..c43a4f33af49d 100644 --- a/x-pack/plugins/alerting/server/routes/lib/rewrite_actions.ts +++ b/x-pack/plugins/alerting/server/routes/lib/rewrite_actions.ts @@ -26,7 +26,7 @@ export const rewriteActionsReq = ( return { id: action.id, params: action.params, - uuid: action.uuid, + ...(action.uuid ? { uuid: action.uuid } : {}), type: RuleActionTypes.SYSTEM, }; } @@ -85,3 +85,11 @@ export const rewriteActionsRes = ( }; }); }; + +export const rewriteActionsResLegacy = ( + actions?: T[] +): Array> => { + if (!actions) return []; + + return actions.map(({ type, ...restAction }) => restAction); +};