[ResponseOps][Rules] Version update api key rule route (#188722)

## Summary

Parent Issue: #187572

Versions the `POST /rule/{id}/_update_api_key` endpoint.

### Checklist
- [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
This commit is contained in:
Antonio 2024-07-24 13:04:35 +02:00 committed by GitHub
parent 4180aa4967
commit 317154df43
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 192 additions and 73 deletions

View file

@ -0,0 +1,12 @@
/*
* 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.
*/
export { updateApiKeyParamsSchema } from './schemas/latest';
export type { UpdateApiKeyParams } from './types/latest';
export { updateApiKeyParamsSchema as updateApiKeyParamsSchemaV1 } from './schemas/v1';
export type { UpdateApiKeyParams as UpdateApiKeyParamsV1 } from './types/v1';

View file

@ -0,0 +1,8 @@
/*
* 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.
*/
export * from './v1';

View file

@ -0,0 +1,12 @@
/*
* 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 { schema } from '@kbn/config-schema';
export const updateApiKeyParamsSchema = schema.object({
id: schema.string(),
});

View file

@ -0,0 +1,8 @@
/*
* 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.
*/
export * from './v1';

View file

@ -0,0 +1,11 @@
/*
* 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 type { TypeOf } from '@kbn/config-schema';
import { updateApiKeyParamsSchemaV1 } from '..';
export type UpdateApiKeyParams = TypeOf<typeof updateApiKeyParamsSchemaV1>;

View file

@ -0,0 +1,11 @@
/*
* 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.
*/
export type { updateApiKeyParamsSchema } from './schemas';
export type { UpdateApiKeyParams } from './types';
export { updateRuleApiKey } from './update_rule_api_key';

View file

@ -0,0 +1,8 @@
/*
* 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.
*/
export * from './update_rule_api_key_schemas';

View file

@ -0,0 +1,12 @@
/*
* 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 { schema } from '@kbn/config-schema';
export const updateApiKeyParamsSchema = schema.object({
id: schema.string(),
});

View file

@ -0,0 +1,8 @@
/*
* 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.
*/
export * from './update_rule_api_key_types';

View file

@ -0,0 +1,11 @@
/*
* 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 { TypeOf } from '@kbn/config-schema';
import { updateApiKeyParamsSchema } from '../schemas';
export type UpdateApiKeyParams = TypeOf<typeof updateApiKeyParamsSchema>;

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { RulesClient, ConstructorOptions } from '../rules_client';
import { RulesClient, ConstructorOptions } from '../../../../rules_client/rules_client';
import {
savedObjectsClientMock,
loggingSystemMock,
@ -13,20 +13,20 @@ import {
uiSettingsServiceMock,
} from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
import { ruleTypeRegistryMock } from '../../../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../../../authorization/alerting_authorization.mock';
import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks';
import { actionsAuthorizationMock } from '@kbn/actions-plugin/server/mocks';
import { AlertingAuthorization } from '../../authorization/alerting_authorization';
import { AlertingAuthorization } from '../../../../authorization/alerting_authorization';
import { ActionsAuthorization } from '@kbn/actions-plugin/server';
import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks';
import { getBeforeSetup, setGlobalDate } from './lib';
import { bulkMarkApiKeysForInvalidation } from '../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation';
import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry';
import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects';
import { backfillClientMock } from '../../backfill_client/backfill_client.mock';
import { getBeforeSetup, setGlobalDate } from '../../../../rules_client/tests/lib';
import { bulkMarkApiKeysForInvalidation } from '../../../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation';
import { ConnectorAdapterRegistry } from '../../../../connector_adapters/connector_adapter_registry';
import { RULE_SAVED_OBJECT_TYPE } from '../../../../saved_objects';
import { backfillClientMock } from '../../../../backfill_client/backfill_client.mock';
jest.mock('../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation', () => ({
jest.mock('../../../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation', () => ({
bulkMarkApiKeysForInvalidation: jest.fn(),
}));
@ -77,7 +77,7 @@ beforeEach(() => {
setGlobalDate();
describe('updateApiKey()', () => {
describe('updateRuleApiKey()', () => {
let rulesClient: RulesClient;
const existingAlert = {
id: '1',
@ -123,7 +123,7 @@ describe('updateApiKey()', () => {
apiKeysEnabled: true,
result: { id: '234', name: '123', api_key: 'abc' },
});
await rulesClient.updateApiKey({ id: '1' });
await rulesClient.updateRuleApiKey({ id: '1' });
expect(unsecuredSavedObjectsClient.get).not.toHaveBeenCalled();
expect(encryptedSavedObjects.getDecryptedAsInternalUser).toHaveBeenCalledWith(
RULE_SAVED_OBJECT_TYPE,
@ -184,7 +184,7 @@ describe('updateApiKey()', () => {
apiKeysEnabled: true,
result: { id: '234', name: '123', api_key: 'abc' },
});
await rulesClient.updateApiKey({ id: '1' });
await rulesClient.updateRuleApiKey({ id: '1' });
expect(unsecuredSavedObjectsClient.get).not.toHaveBeenCalled();
expect(encryptedSavedObjects.getDecryptedAsInternalUser).toHaveBeenCalledWith(
RULE_SAVED_OBJECT_TYPE,
@ -240,7 +240,7 @@ describe('updateApiKey()', () => {
apiKeysEnabled: true,
result: { id: '234', name: '123', api_key: 'abc' },
});
await rulesClient.updateApiKey({ id: '1' });
await rulesClient.updateRuleApiKey({ id: '1' });
expect(unsecuredSavedObjectsClient.get).not.toHaveBeenCalled();
expect(encryptedSavedObjects.getDecryptedAsInternalUser).toHaveBeenCalledWith(
RULE_SAVED_OBJECT_TYPE,
@ -288,12 +288,21 @@ describe('updateApiKey()', () => {
throw new Error('no');
});
await expect(
async () => await rulesClient.updateApiKey({ id: '1' })
async () => await rulesClient.updateRuleApiKey({ id: '1' })
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Error updating API key for rule: could not create API key - no"`
);
});
test('throws an error if API params do not match the schema', async () => {
await expect(
// @ts-ignore: this is what we are testing
async () => await rulesClient.updateRuleApiKey({ id: 1 })
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Error validating update api key parameters - [id]: expected value of type [string] but got [number]"`
);
});
test('falls back to SOC when getDecryptedAsInternalUser throws an error', async () => {
rulesClientParams.createAPIKey.mockResolvedValueOnce({
apiKeysEnabled: true,
@ -301,7 +310,7 @@ describe('updateApiKey()', () => {
});
encryptedSavedObjects.getDecryptedAsInternalUser.mockRejectedValueOnce(new Error('Fail'));
await rulesClient.updateApiKey({ id: '1' });
await rulesClient.updateRuleApiKey({ id: '1' });
expect(unsecuredSavedObjectsClient.get).toHaveBeenCalledWith(RULE_SAVED_OBJECT_TYPE, '1');
expect(encryptedSavedObjects.getDecryptedAsInternalUser).toHaveBeenCalledWith(
RULE_SAVED_OBJECT_TYPE,
@ -346,7 +355,7 @@ describe('updateApiKey()', () => {
test('swallows error when invalidate API key throws', async () => {
bulkMarkApiKeysForInvalidationMock.mockImplementationOnce(() => new Error('Fail'));
await rulesClient.updateApiKey({ id: '1' });
await rulesClient.updateRuleApiKey({ id: '1' });
expect(unsecuredSavedObjectsClient.update).toHaveBeenCalled();
expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledTimes(1);
expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledWith(
@ -359,7 +368,7 @@ describe('updateApiKey()', () => {
test('swallows error when getting decrypted object throws', async () => {
encryptedSavedObjects.getDecryptedAsInternalUser.mockRejectedValueOnce(new Error('Fail'));
await rulesClient.updateApiKey({ id: '1' });
await rulesClient.updateRuleApiKey({ id: '1' });
expect(rulesClientParams.logger.error).toHaveBeenCalledWith(
'updateApiKey(): Failed to load API key to invalidate on alert 1: Fail'
);
@ -373,9 +382,9 @@ describe('updateApiKey()', () => {
});
unsecuredSavedObjectsClient.update.mockRejectedValueOnce(new Error('Fail'));
await expect(rulesClient.updateApiKey({ id: '1' })).rejects.toThrowErrorMatchingInlineSnapshot(
`"Fail"`
);
await expect(
rulesClient.updateRuleApiKey({ id: '1' })
).rejects.toThrowErrorMatchingInlineSnapshot(`"Fail"`);
expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledTimes(1);
expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledWith(
{ apiKeys: ['MjM0OmFiYw=='] },
@ -385,8 +394,8 @@ describe('updateApiKey()', () => {
});
describe('authorization', () => {
test('ensures user is authorised to updateApiKey this type of alert under the consumer', async () => {
await rulesClient.updateApiKey({ id: '1' });
test('ensures user is authorised to updateRuleApiKey this type of alert under the consumer', async () => {
await rulesClient.updateRuleApiKey({ id: '1' });
expect(actionsAuthorization.ensureAuthorized).toHaveBeenCalledWith({ operation: 'execute' });
expect(authorization.ensureAuthorized).toHaveBeenCalledWith({
@ -397,13 +406,13 @@ describe('updateApiKey()', () => {
});
});
test('throws when user is not authorised to updateApiKey this type of alert', async () => {
test('throws when user is not authorised to updateRuleApiKey this type of alert', async () => {
authorization.ensureAuthorized.mockRejectedValue(
new Error(`Unauthorized to updateApiKey a "myType" alert for "myApp"`)
new Error(`Unauthorized to updateRuleApiKey a "myType" alert for "myApp"`)
);
await expect(rulesClient.updateApiKey({ id: '1' })).rejects.toMatchInlineSnapshot(
`[Error: Unauthorized to updateApiKey a "myType" alert for "myApp"]`
await expect(rulesClient.updateRuleApiKey({ id: '1' })).rejects.toMatchInlineSnapshot(
`[Error: Unauthorized to updateRuleApiKey a "myType" alert for "myApp"]`
);
expect(authorization.ensureAuthorized).toHaveBeenCalledWith({
@ -417,7 +426,7 @@ describe('updateApiKey()', () => {
describe('auditLogger', () => {
test('logs audit event when updating the API key of a rule', async () => {
await rulesClient.updateApiKey({ id: '1' });
await rulesClient.updateRuleApiKey({ id: '1' });
expect(auditLogger.log).toHaveBeenCalledWith(
expect.objectContaining({
@ -433,7 +442,7 @@ describe('updateApiKey()', () => {
test('logs audit event when not authorised to update the API key of a rule', async () => {
authorization.ensureAuthorized.mockRejectedValue(new Error('Unauthorized'));
await expect(rulesClient.updateApiKey({ id: '1' })).rejects.toThrow();
await expect(rulesClient.updateRuleApiKey({ id: '1' })).rejects.toThrow();
expect(auditLogger.log).toHaveBeenCalledWith(
expect.objectContaining({
event: expect.objectContaining({

View file

@ -5,32 +5,41 @@
* 2.0.
*/
import { RawRule } from '../../types';
import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization';
import { retryIfConflicts } from '../../lib/retry_if_conflicts';
import { bulkMarkApiKeysForInvalidation } from '../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation';
import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events';
import { createNewAPIKeySet, updateMeta } from '../lib';
import { RulesClientContext } from '../types';
import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects';
import Boom from '@hapi/boom';
import { RawRule } from '../../../../types';
import { WriteOperations, AlertingAuthorizationEntity } from '../../../../authorization';
import { retryIfConflicts } from '../../../../lib/retry_if_conflicts';
import { bulkMarkApiKeysForInvalidation } from '../../../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation';
import { ruleAuditEvent, RuleAuditAction } from '../../../../rules_client/common/audit_events';
import { createNewAPIKeySet, updateMeta } from '../../../../rules_client/lib';
import { RulesClientContext } from '../../../../rules_client/types';
import { RULE_SAVED_OBJECT_TYPE } from '../../../../saved_objects';
import { UpdateApiKeyParams } from './types';
import { updateApiKeyParamsSchema } from './schemas';
export async function updateApiKey(
export async function updateRuleApiKey(
context: RulesClientContext,
{ id }: { id: string }
{ id }: UpdateApiKeyParams
): Promise<void> {
return await retryIfConflicts(
context.logger,
`rulesClient.updateApiKey('${id}')`,
`rulesClient.updateRuleApiKey('${id}')`,
async () => await updateApiKeyWithOCC(context, { id })
);
}
async function updateApiKeyWithOCC(context: RulesClientContext, { id }: { id: string }) {
async function updateApiKeyWithOCC(context: RulesClientContext, { id }: UpdateApiKeyParams) {
let oldApiKeyToInvalidate: string | null = null;
let oldApiKeyCreatedByUser: boolean | undefined | null = false;
let attributes: RawRule;
let version: string | undefined;
try {
updateApiKeyParamsSchema.validate({ id });
} catch (error) {
throw Boom.badRequest(`Error validating update api key parameters - ${error.message}`);
}
try {
const decryptedAlert =
await context.encryptedSavedObjectsClient.getDecryptedAsInternalUser<RawRule>(

View file

@ -35,7 +35,7 @@ import { muteAllRuleRoute } from './mute_all_rule';
import { muteAlertRoute } from './rule/apis/mute_alert/mute_alert';
import { unmuteAllRuleRoute } from './unmute_all_rule';
import { unmuteAlertRoute } from './unmute_alert';
import { updateRuleApiKeyRoute } from './update_rule_api_key';
import { updateRuleApiKeyRoute } from './rule/apis/update_api_key/update_rule_api_key_route';
import { bulkEditInternalRulesRoute } from './rule/apis/bulk_edit/bulk_edit_rules_route';
import { snoozeRuleRoute } from './rule/apis/snooze';
import { unsnoozeRuleRoute } from './rule/apis/unsnooze';

View file

@ -38,7 +38,7 @@ describe('updateApiKeyRoute', () => {
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/_update_api_key"`);
rulesClient.updateApiKey.mockResolvedValueOnce();
rulesClient.updateRuleApiKey.mockResolvedValueOnce();
const [context, req, res] = mockHandlerArguments(
{ rulesClient },
@ -52,8 +52,8 @@ describe('updateApiKeyRoute', () => {
expect(await handler(context, req, res)).toEqual(undefined);
expect(rulesClient.updateApiKey).toHaveBeenCalledTimes(1);
expect(rulesClient.updateApiKey.mock.calls[0]).toMatchInlineSnapshot(`
expect(rulesClient.updateRuleApiKey).toHaveBeenCalledTimes(1);
expect(rulesClient.updateRuleApiKey.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Object {
"id": "1",
@ -72,7 +72,7 @@ describe('updateApiKeyRoute', () => {
const [, handler] = router.post.mock.calls[0];
rulesClient.updateApiKey.mockRejectedValue(
rulesClient.updateRuleApiKey.mockRejectedValue(
new RuleTypeDisabledError('Fail', 'license_invalid')
);

View file

@ -41,7 +41,7 @@ export const updateApiKeyRoute = (
const rulesClient = (await context.alerting).getRulesClient();
const { id } = req.params;
try {
await rulesClient.updateApiKey({ id });
await rulesClient.updateRuleApiKey({ id });
return res.noContent();
} catch (e) {
if (e instanceof RuleTypeDisabledError) {

View file

@ -5,15 +5,15 @@
* 2.0.
*/
import { updateRuleApiKeyRoute } from './update_rule_api_key';
import { updateRuleApiKeyRoute } from './update_rule_api_key_route';
import { httpServiceMock } from '@kbn/core/server/mocks';
import { licenseStateMock } from '../lib/license_state.mock';
import { mockHandlerArguments } from './_mock_handler_arguments';
import { rulesClientMock } from '../rules_client.mock';
import { RuleTypeDisabledError } from '../lib/errors/rule_type_disabled';
import { licenseStateMock } from '../../../../lib/license_state.mock';
import { mockHandlerArguments } from '../../../_mock_handler_arguments';
import { rulesClientMock } from '../../../../rules_client.mock';
import { RuleTypeDisabledError } from '../../../../lib/errors/rule_type_disabled';
const rulesClient = rulesClientMock.create();
jest.mock('../lib/license_api_access', () => ({
jest.mock('../../../../lib/license_api_access', () => ({
verifyApiAccess: jest.fn(),
}));
@ -32,7 +32,7 @@ describe('updateRuleApiKeyRoute', () => {
expect(config.path).toMatchInlineSnapshot(`"/api/alerting/rule/{id}/_update_api_key"`);
rulesClient.updateApiKey.mockResolvedValueOnce();
rulesClient.updateRuleApiKey.mockResolvedValueOnce();
const [context, req, res] = mockHandlerArguments(
{ rulesClient },
@ -46,8 +46,8 @@ describe('updateRuleApiKeyRoute', () => {
expect(await handler(context, req, res)).toEqual(undefined);
expect(rulesClient.updateApiKey).toHaveBeenCalledTimes(1);
expect(rulesClient.updateApiKey.mock.calls[0]).toMatchInlineSnapshot(`
expect(rulesClient.updateRuleApiKey).toHaveBeenCalledTimes(1);
expect(rulesClient.updateRuleApiKey.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Object {
"id": "1",
@ -66,7 +66,7 @@ describe('updateRuleApiKeyRoute', () => {
const [, handler] = router.post.mock.calls[0];
rulesClient.updateApiKey.mockRejectedValue(
rulesClient.updateRuleApiKey.mockRejectedValue(
new RuleTypeDisabledError('Fail', 'license_invalid')
);

View file

@ -6,14 +6,13 @@
*/
import { IRouter } from '@kbn/core/server';
import { schema } from '@kbn/config-schema';
import { ILicenseState, RuleTypeDisabledError } from '../lib';
import { verifyAccessAndContext } from './lib';
import { AlertingRequestHandlerContext, BASE_ALERTING_API_PATH } from '../types';
const paramSchema = schema.object({
id: schema.string(),
});
import {
UpdateApiKeyParamsV1,
updateApiKeyParamsSchemaV1,
} from '../../../../../common/routes/rule/apis/update_api_key';
import { ILicenseState, RuleTypeDisabledError } from '../../../../lib';
import { verifyAccessAndContext } from '../../../lib';
import { AlertingRequestHandlerContext, BASE_ALERTING_API_PATH } from '../../../../types';
export const updateRuleApiKeyRoute = (
router: IRouter<AlertingRequestHandlerContext>,
@ -24,18 +23,19 @@ export const updateRuleApiKeyRoute = (
path: `${BASE_ALERTING_API_PATH}/rule/{id}/_update_api_key`,
options: {
access: 'public',
summary: `Update the API key for a rule`,
summary: 'Update the API key for a rule',
},
validate: {
params: paramSchema,
params: updateApiKeyParamsSchemaV1,
},
},
router.handleLegacyErrors(
verifyAccessAndContext(licenseState, async function (context, req, res) {
const rulesClient = (await context.alerting).getRulesClient();
const { id } = req.params;
const { id }: UpdateApiKeyParamsV1 = req.params;
try {
await rulesClient.updateApiKey({ id });
await rulesClient.updateRuleApiKey({ id });
return res.noContent();
} catch (e) {
if (e instanceof RuleTypeDisabledError) {

View file

@ -23,7 +23,7 @@ const createRulesClientMock = () => {
update: jest.fn(),
enable: jest.fn(),
disable: jest.fn(),
updateApiKey: jest.fn(),
updateRuleApiKey: jest.fn(),
muteAll: jest.fn(),
unmuteAll: jest.fn(),
muteInstance: jest.fn(),

View file

@ -53,7 +53,7 @@ import {
BulkEditOptions,
} from '../application/rule/methods/bulk_edit/bulk_edit_rules';
import { bulkEnableRules, BulkEnableRulesParams } from '../application/rule/methods/bulk_enable';
import { updateApiKey } from './methods/update_api_key';
import { updateRuleApiKey } from '../application/rule/methods/update_api_key/update_rule_api_key';
import { enable } from './methods/enable';
import { disable } from './methods/disable';
import { clearExpiredSnoozes } from './methods/clear_expired_snoozes';
@ -164,7 +164,7 @@ export class RulesClient {
public bulkDisableRules = (options: BulkDisableRulesRequestBody) =>
bulkDisableRules(this.context, options);
public updateApiKey = (options: { id: string }) => updateApiKey(this.context, options);
public updateRuleApiKey = (params: { id: string }) => updateRuleApiKey(this.context, params);
public enable = (options: { id: string }) => enable(this.context, options);
public disable = (options: { id: string; untrack?: boolean }) => disable(this.context, options);

View file

@ -149,7 +149,7 @@ async function update(success: boolean) {
async function updateApiKey(success: boolean) {
try {
await rulesClient.updateApiKey({ id: MockRuleId });
await rulesClient.updateRuleApiKey({ id: MockRuleId });
} catch (err) {
return expectConflict(success, err);
}