From cf1f8fe46b80767b3a5be8462e7b466b9e6ce3c1 Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Wed, 5 Aug 2026 15:45:03 +0000 Subject: [PATCH 1/4] feat(gateway): add delete commands --- src/core/gateway.delete.test.ts | 50 +++++++++ src/core/gateway.tsx | 37 +++++++ src/handlers/gateway/delete/index.tsx | 21 ++++ src/handlers/gateway/gateway.delete.test.tsx | 104 +++++++++++++++++++ src/handlers/gateway/gateway.test.tsx | 3 + src/handlers/gateway/index.tsx | 2 + src/handlers/gateway/rule/delete/index.tsx | 33 ++++++ src/handlers/gateway/rule/index.tsx | 4 +- src/handlers/gateway/target/delete/index.tsx | 33 ++++++ src/handlers/gateway/target/index.tsx | 4 +- src/handlers/gateway/types.tsx | 14 +++ src/testing/TestCoreClient.tsx | 50 +++++++++ 12 files changed, 353 insertions(+), 2 deletions(-) create mode 100644 src/core/gateway.delete.test.ts create mode 100644 src/handlers/gateway/delete/index.tsx create mode 100644 src/handlers/gateway/gateway.delete.test.tsx create mode 100644 src/handlers/gateway/rule/delete/index.tsx create mode 100644 src/handlers/gateway/target/delete/index.tsx diff --git a/src/core/gateway.delete.test.ts b/src/core/gateway.delete.test.ts new file mode 100644 index 000000000..1ba54d5d9 --- /dev/null +++ b/src/core/gateway.delete.test.ts @@ -0,0 +1,50 @@ +import { expect, test } from "bun:test"; +import { + DeleteGatewayCommand, + DeleteGatewayRuleCommand, + DeleteGatewayTargetCommand, + type BedrockAgentCoreControlClient, +} from "@aws-sdk/client-bedrock-agentcore-control"; +import { GatewayClient } from "./gateway"; +import type { AwsClients } from "./types"; + +test("maps Gateway, Target, and Rule selectors to their delete commands", async () => { + const commands: unknown[] = []; + const control = { + send: async (command: unknown) => { + commands.push(command); + return {}; + }, + } as unknown as BedrockAgentCoreControlClient; + const clients: AwsClients = { + control: () => control, + data: () => { + throw new Error("unexpected data client"); + }, + iam: () => { + throw new Error("unexpected IAM client"); + }, + }; + const gateway = new GatewayClient(clients); + const options = { region: "us-west-2" }; + + await gateway.deleteGateway("gateway-1", options); + await gateway.deleteGatewayTarget("gateway-1", "target-1", options); + await gateway.deleteGatewayRule("gateway-1", "rule-1", options); + + expect(commands).toHaveLength(3); + expect(commands[0]).toBeInstanceOf(DeleteGatewayCommand); + expect((commands[0] as DeleteGatewayCommand).input).toEqual({ + gatewayIdentifier: "gateway-1", + }); + expect(commands[1]).toBeInstanceOf(DeleteGatewayTargetCommand); + expect((commands[1] as DeleteGatewayTargetCommand).input).toEqual({ + gatewayIdentifier: "gateway-1", + targetId: "target-1", + }); + expect(commands[2]).toBeInstanceOf(DeleteGatewayRuleCommand); + expect((commands[2] as DeleteGatewayRuleCommand).input).toEqual({ + gatewayIdentifier: "gateway-1", + ruleId: "rule-1", + }); +}); diff --git a/src/core/gateway.tsx b/src/core/gateway.tsx index 8dca91334..e5a60258e 100644 --- a/src/core/gateway.tsx +++ b/src/core/gateway.tsx @@ -2,6 +2,9 @@ import { CreateGatewayCommand, CreateGatewayRuleCommand, CreateGatewayTargetCommand, + DeleteGatewayCommand, + DeleteGatewayRuleCommand, + DeleteGatewayTargetCommand, GetGatewayCommand, GetGatewayRuleCommand, GetGatewayTargetCommand, @@ -14,6 +17,9 @@ import { type CreateGatewayResponse, type CreateGatewayRuleResponse, type CreateGatewayTargetResponse, + type DeleteGatewayResponse, + type DeleteGatewayRuleResponse, + type DeleteGatewayTargetResponse, type GetGatewayResponse, type GetGatewayRuleResponse, type GetGatewayTargetResponse, @@ -144,6 +150,12 @@ export class GatewayClient implements CoreGatewayClient { .send(new ListGatewaysCommand({ nextToken, maxResults })); } + async deleteGateway(id: string, options: CoreOptions): Promise { + return this.clients + .control(toClientConfig(options)) + .send(new DeleteGatewayCommand({ gatewayIdentifier: id })); + } + async getGatewayTarget( gatewayId: string, targetId: string, @@ -195,6 +207,19 @@ export class GatewayClient implements CoreGatewayClient { return this.updateTarget(patch, options, true); } + async deleteGatewayTarget( + gatewayId: string, + targetId: string, + options: CoreOptions, + ): Promise { + return this.clients.control(toClientConfig(options)).send( + new DeleteGatewayTargetCommand({ + gatewayIdentifier: gatewayId, + targetId, + }), + ); + } + async getGatewayRule( gatewayId: string, ruleId: string, @@ -236,6 +261,18 @@ export class GatewayClient implements CoreGatewayClient { ): Promise { return this.clients.control(toClientConfig(options)).send(new UpdateGatewayRuleCommand(input)); } + async deleteGatewayRule( + gatewayId: string, + ruleId: string, + options: CoreOptions, + ): Promise { + return this.clients.control(toClientConfig(options)).send( + new DeleteGatewayRuleCommand({ + gatewayIdentifier: gatewayId, + ruleId, + }), + ); + } private async updateTarget( patch: GatewayTargetUpdatePatch, diff --git a/src/handlers/gateway/delete/index.tsx b/src/handlers/gateway/delete/index.tsx new file mode 100644 index 000000000..ddd27389a --- /dev/null +++ b/src/handlers/gateway/delete/index.tsx @@ -0,0 +1,21 @@ +import z from "zod"; +import { InputValidationError } from "../../../errors"; +import { createHandler, flag } from "../../../router"; +import { JsonRendererKey } from "../../../tui"; +import type { Core } from "../../types"; +import { coreOptsFromCtx } from "../../utils"; + +export const createDeleteGatewayHandler = (core: Core) => + createHandler({ + name: "delete", + description: "delete an AgentCore Gateway", + flags: [flag("id", "the Gateway ID", z.string().optional())], + handle: async (ctx, flags) => { + if (!flags.id) { + throw new InputValidationError("required option '--id ' not specified"); + } + ctx + .require(JsonRendererKey) + .renderJson(await core.gateway.deleteGateway(flags.id, coreOptsFromCtx(ctx))); + }, + }); diff --git a/src/handlers/gateway/gateway.delete.test.tsx b/src/handlers/gateway/gateway.delete.test.tsx new file mode 100644 index 000000000..d70802af2 --- /dev/null +++ b/src/handlers/gateway/gateway.delete.test.tsx @@ -0,0 +1,104 @@ +import { describe, expect, test } from "bun:test"; +import type { + DeleteGatewayResponse, + DeleteGatewayRuleResponse, + DeleteGatewayTargetResponse, +} from "@aws-sdk/client-bedrock-agentcore-control"; +import { + createSilentLogger, + TestCoreClient, + TestGlobalConfigAccessor, + testIO, +} from "../../testing"; +import { createRootHandler } from "../index"; + +const REGION = "us-west-2"; +const GATEWAY_ID = "gateway-1"; +const TARGET_ID = "target-1"; +const RULE_ID = "rule-1"; + +async function run( + args: string[], + core = new TestCoreClient(), +): Promise<{ core: TestCoreClient; stdout: string }> { + const io = testIO(); + const root = createRootHandler(core, { + io: io.io, + logger: createSilentLogger(), + globalConfigAccessor: new TestGlobalConfigAccessor(), + }); + + await root.route(["node", "agentcore", ...args, "--region", REGION]); + return { core, stdout: io.stdout() }; +} + +describe("gateway delete commands", () => { + test("deletes a Gateway", async () => { + const response = { gatewayId: GATEWAY_ID, status: "DELETING" } as DeleteGatewayResponse; + const core = new TestCoreClient(); + core.gateway.setDeleteResponse(response); + + const result = await run(["gateway", "delete", "--id", GATEWAY_ID], core); + + expect(core.gateway.calls).toEqual([ + { + method: "deleteGateway", + args: [GATEWAY_ID, { region: REGION }], + }, + ]); + expect(JSON.parse(result.stdout)).toEqual(response); + }); + + test("deletes a Target", async () => { + const response = { targetId: TARGET_ID, status: "DELETING" } as DeleteGatewayTargetResponse; + const core = new TestCoreClient(); + core.gateway.setDeleteTargetResponse(response); + + const result = await run( + ["gateway", "target", "delete", "--gateway-id", GATEWAY_ID, "--target-id", TARGET_ID], + core, + ); + + expect(core.gateway.calls).toEqual([ + { + method: "deleteGatewayTarget", + args: [GATEWAY_ID, TARGET_ID, { region: REGION }], + }, + ]); + expect(JSON.parse(result.stdout)).toEqual(response); + }); + + test("deletes a Rule", async () => { + const response = { ruleId: RULE_ID, status: "DELETING" } as DeleteGatewayRuleResponse; + const core = new TestCoreClient(); + core.gateway.setDeleteRuleResponse(response); + + const result = await run( + ["gateway", "rule", "delete", "--gateway-id", GATEWAY_ID, "--rule-id", RULE_ID], + core, + ); + + expect(core.gateway.calls).toEqual([ + { + method: "deleteGatewayRule", + args: [GATEWAY_ID, RULE_ID, { region: REGION }], + }, + ]); + expect(JSON.parse(result.stdout)).toEqual(response); + }); +}); + +describe("gateway delete validation", () => { + test.each([ + ["Gateway selector", ["gateway", "delete"], /--id/], + ["Target parent", ["gateway", "target", "delete"], /--gateway-id/], + ["Target selector", ["gateway", "target", "delete", "--gateway-id", GATEWAY_ID], /--target-id/], + ["Rule parent", ["gateway", "rule", "delete"], /--gateway-id/], + ["Rule selector", ["gateway", "rule", "delete", "--gateway-id", GATEWAY_ID], /--rule-id/], + ] as const)("rejects a missing %s before calling Core", async (_name, args, error) => { + const core = new TestCoreClient(); + + await expect(run([...args], core)).rejects.toThrow(error); + expect(core.gateway.calls).toEqual([]); + }); +}); diff --git a/src/handlers/gateway/gateway.test.tsx b/src/handlers/gateway/gateway.test.tsx index aa73ccec6..b46212e3f 100644 --- a/src/handlers/gateway/gateway.test.tsx +++ b/src/handlers/gateway/gateway.test.tsx @@ -76,6 +76,7 @@ describe("gateway command hierarchy", () => { "update", "get", "list", + "delete", "target", "connector", "rule", @@ -85,6 +86,7 @@ describe("gateway command hierarchy", () => { "update", "get", "list", + "delete", ]); expect(connector?.children().map((child) => child.name())).toEqual([ "create", @@ -97,6 +99,7 @@ describe("gateway command hierarchy", () => { "update", "get", "list", + "delete", ]); }); diff --git a/src/handlers/gateway/index.tsx b/src/handlers/gateway/index.tsx index 7d3e62409..891aaf4c5 100644 --- a/src/handlers/gateway/index.tsx +++ b/src/handlers/gateway/index.tsx @@ -4,6 +4,7 @@ import { createHelpDefault } from "../help"; import type { Core } from "../types"; import { createGatewayConnectorHandler } from "./connector"; import { createCreateGatewayHandler } from "./create"; +import { createDeleteGatewayHandler } from "./delete"; import { createGetGatewayHandler } from "./get"; import { createListGatewaysHandler } from "./list"; import { createGatewayRuleHandler } from "./rule"; @@ -17,6 +18,7 @@ export function createGatewayHandler(core: Core, io: AppIO): Router { .handler(createUpdateGatewayHandler(core, io)) .handler(createGetGatewayHandler(core)) .handler(createListGatewaysHandler(core)) + .handler(createDeleteGatewayHandler(core)) .handler(createGatewayTargetHandler(core, io)) .handler(createGatewayConnectorHandler(core, io)) .handler(createGatewayRuleHandler(core, io)); diff --git a/src/handlers/gateway/rule/delete/index.tsx b/src/handlers/gateway/rule/delete/index.tsx new file mode 100644 index 000000000..b79ec0eaa --- /dev/null +++ b/src/handlers/gateway/rule/delete/index.tsx @@ -0,0 +1,33 @@ +import z from "zod"; +import { InputValidationError } from "../../../../errors"; +import { createHandler, flag } from "../../../../router"; +import { JsonRendererKey } from "../../../../tui"; +import type { Core } from "../../../types"; +import { coreOptsFromCtx } from "../../../utils"; + +export const createDeleteGatewayRuleHandler = (core: Core) => + createHandler({ + name: "delete", + description: "delete a Gateway Rule", + flags: [ + flag("gateway-id", "the parent Gateway ID", z.string().optional()), + flag("rule-id", "the Rule ID", z.string().optional()), + ], + handle: async (ctx, flags) => { + if (!flags["gateway-id"]) { + throw new InputValidationError("required option '--gateway-id ' not specified"); + } + if (!flags["rule-id"]) { + throw new InputValidationError("required option '--rule-id ' not specified"); + } + ctx + .require(JsonRendererKey) + .renderJson( + await core.gateway.deleteGatewayRule( + flags["gateway-id"], + flags["rule-id"], + coreOptsFromCtx(ctx), + ), + ); + }, + }); diff --git a/src/handlers/gateway/rule/index.tsx b/src/handlers/gateway/rule/index.tsx index bb7869dfd..4f54a3b4f 100644 --- a/src/handlers/gateway/rule/index.tsx +++ b/src/handlers/gateway/rule/index.tsx @@ -3,6 +3,7 @@ import { Router } from "../../../router"; import { createHelpDefault } from "../../help"; import type { Core } from "../../types"; import { createCreateGatewayRuleHandler } from "./create"; +import { createDeleteGatewayRuleHandler } from "./delete"; import { createGetGatewayRuleHandler } from "./get"; import { createListGatewayRulesHandler } from "./list"; import { createUpdateGatewayRuleHandler } from "./update"; @@ -13,5 +14,6 @@ export function createGatewayRuleHandler(core: Core, io: AppIO): Router { .handler(createCreateGatewayRuleHandler(core, io)) .handler(createUpdateGatewayRuleHandler(core, io)) .handler(createGetGatewayRuleHandler(core)) - .handler(createListGatewayRulesHandler(core)); + .handler(createListGatewayRulesHandler(core)) + .handler(createDeleteGatewayRuleHandler(core)); } diff --git a/src/handlers/gateway/target/delete/index.tsx b/src/handlers/gateway/target/delete/index.tsx new file mode 100644 index 000000000..75c3626fb --- /dev/null +++ b/src/handlers/gateway/target/delete/index.tsx @@ -0,0 +1,33 @@ +import z from "zod"; +import { InputValidationError } from "../../../../errors"; +import { createHandler, flag } from "../../../../router"; +import { JsonRendererKey } from "../../../../tui"; +import type { Core } from "../../../types"; +import { coreOptsFromCtx } from "../../../utils"; + +export const createDeleteGatewayTargetHandler = (core: Core) => + createHandler({ + name: "delete", + description: "delete a Gateway Target", + flags: [ + flag("gateway-id", "the parent Gateway ID", z.string().optional()), + flag("target-id", "the Target ID", z.string().optional()), + ], + handle: async (ctx, flags) => { + if (!flags["gateway-id"]) { + throw new InputValidationError("required option '--gateway-id ' not specified"); + } + if (!flags["target-id"]) { + throw new InputValidationError("required option '--target-id ' not specified"); + } + ctx + .require(JsonRendererKey) + .renderJson( + await core.gateway.deleteGatewayTarget( + flags["gateway-id"], + flags["target-id"], + coreOptsFromCtx(ctx), + ), + ); + }, + }); diff --git a/src/handlers/gateway/target/index.tsx b/src/handlers/gateway/target/index.tsx index 59ffa3043..d7076257a 100644 --- a/src/handlers/gateway/target/index.tsx +++ b/src/handlers/gateway/target/index.tsx @@ -3,6 +3,7 @@ import { Router } from "../../../router"; import { createHelpDefault } from "../../help"; import type { Core } from "../../types"; import { createCreateGatewayTargetHandler } from "./create"; +import { createDeleteGatewayTargetHandler } from "./delete"; import { createGetGatewayTargetHandler } from "./get"; import { createListGatewayTargetsHandler } from "./list"; import { createUpdateGatewayTargetHandler } from "./update"; @@ -13,5 +14,6 @@ export function createGatewayTargetHandler(core: Core, io: AppIO): Router { .handler(createCreateGatewayTargetHandler(core, io)) .handler(createUpdateGatewayTargetHandler(core, io)) .handler(createGetGatewayTargetHandler(core)) - .handler(createListGatewayTargetsHandler(core)); + .handler(createListGatewayTargetsHandler(core)) + .handler(createDeleteGatewayTargetHandler(core)); } diff --git a/src/handlers/gateway/types.tsx b/src/handlers/gateway/types.tsx index ccb215277..2b5e8c421 100644 --- a/src/handlers/gateway/types.tsx +++ b/src/handlers/gateway/types.tsx @@ -7,6 +7,9 @@ import type { CreateGatewayTargetRequest, CreateGatewayTargetResponse, CustomTransformConfiguration, + DeleteGatewayResponse, + DeleteGatewayRuleResponse, + DeleteGatewayTargetResponse, ExceptionLevel, GatewayInterceptorConfiguration, GatewayPolicyEngineConfiguration, @@ -76,6 +79,7 @@ export interface CoreGatewayClient { maxResults: number | undefined, options: CoreOptions, ): Promise; + deleteGateway(id: string, options: CoreOptions): Promise; getGatewayTarget( gatewayId: string, targetId: string, @@ -99,6 +103,11 @@ export interface CoreGatewayClient { patch: GatewayTargetUpdatePatch, options: CoreOptions, ): Promise; + deleteGatewayTarget( + gatewayId: string, + targetId: string, + options: CoreOptions, + ): Promise; getGatewayRule( gatewayId: string, ruleId: string, @@ -118,4 +127,9 @@ export interface CoreGatewayClient { input: GatewayRuleUpdateInput, options: CoreOptions, ): Promise; + deleteGatewayRule( + gatewayId: string, + ruleId: string, + options: CoreOptions, + ): Promise; } diff --git a/src/testing/TestCoreClient.tsx b/src/testing/TestCoreClient.tsx index 572ffc2f3..383a7cda1 100644 --- a/src/testing/TestCoreClient.tsx +++ b/src/testing/TestCoreClient.tsx @@ -8,6 +8,9 @@ import type { CreateHarnessEndpointResponse, CreateHarnessResponse, DeleteApiKeyCredentialProviderResponse, + DeleteGatewayResponse, + DeleteGatewayRuleResponse, + DeleteGatewayTargetResponse, DeleteOauth2CredentialProviderResponse, DeleteHarnessEndpointRequest, DeleteHarnessEndpointResponse, @@ -178,14 +181,17 @@ const DEFAULT_CREATE_GATEWAY_RESPONSE = {} as CreateGatewayResponse; const DEFAULT_UPDATE_GATEWAY_RESPONSE = {} as UpdateGatewayResponse; const DEFAULT_GET_GATEWAY_RESPONSE = {} as GetGatewayResponse; const DEFAULT_LIST_GATEWAYS_RESPONSE: ListGatewaysResponse = { items: [] }; +const DEFAULT_DELETE_GATEWAY_RESPONSE = {} as DeleteGatewayResponse; const DEFAULT_CREATE_GATEWAY_TARGET_RESPONSE = {} as CreateGatewayTargetResponse; const DEFAULT_UPDATE_GATEWAY_TARGET_RESPONSE = {} as UpdateGatewayTargetResponse; const DEFAULT_GET_GATEWAY_TARGET_RESPONSE = {} as GetGatewayTargetResponse; const DEFAULT_LIST_GATEWAY_TARGETS_RESPONSE: ListGatewayTargetsResponse = { items: [] }; +const DEFAULT_DELETE_GATEWAY_TARGET_RESPONSE = {} as DeleteGatewayTargetResponse; const DEFAULT_CREATE_GATEWAY_RULE_RESPONSE = {} as CreateGatewayRuleResponse; const DEFAULT_UPDATE_GATEWAY_RULE_RESPONSE = {} as UpdateGatewayRuleResponse; const DEFAULT_GET_GATEWAY_RULE_RESPONSE = {} as GetGatewayRuleResponse; const DEFAULT_LIST_GATEWAY_RULES_RESPONSE: ListGatewayRulesResponse = { gatewayRules: [] }; +const DEFAULT_DELETE_GATEWAY_RULE_RESPONSE = {} as DeleteGatewayRuleResponse; const DEFAULT_CREATE_OAUTH2_RESPONSE = {} as CreateOauth2CredentialProviderResponse; const DEFAULT_GET_OAUTH2_RESPONSE = {} as GetOauth2CredentialProviderResponse; const DEFAULT_LIST_OAUTH2_RESPONSE: ListOauth2CredentialProvidersResponse = { @@ -829,10 +835,14 @@ export class TestGatewayClient implements CoreGatewayClient { private getResponse: GetGatewayResponse = DEFAULT_GET_GATEWAY_RESPONSE; private listResponses = new Map(); + private deleteResponse: DeleteGatewayResponse = DEFAULT_DELETE_GATEWAY_RESPONSE; private getTargetResponse: GetGatewayTargetResponse = DEFAULT_GET_GATEWAY_TARGET_RESPONSE; private listTargetResponses = new Map(); + private deleteTargetResponse: DeleteGatewayTargetResponse = + DEFAULT_DELETE_GATEWAY_TARGET_RESPONSE; private getRuleResponse: GetGatewayRuleResponse = DEFAULT_GET_GATEWAY_RULE_RESPONSE; private listRuleResponses = new Map(); + private deleteRuleResponse: DeleteGatewayRuleResponse = DEFAULT_DELETE_GATEWAY_RULE_RESPONSE; private error?: Error; setGetResponse(response: GetGatewayResponse): this { @@ -845,6 +855,11 @@ export class TestGatewayClient implements CoreGatewayClient { return this; } + setDeleteResponse(response: DeleteGatewayResponse): this { + this.deleteResponse = response; + return this; + } + setGetTargetResponse(response: GetGatewayTargetResponse): this { this.getTargetResponse = response; return this; @@ -855,6 +870,11 @@ export class TestGatewayClient implements CoreGatewayClient { return this; } + setDeleteTargetResponse(response: DeleteGatewayTargetResponse): this { + this.deleteTargetResponse = response; + return this; + } + setGetRuleResponse(response: GetGatewayRuleResponse): this { this.getRuleResponse = response; return this; @@ -865,6 +885,11 @@ export class TestGatewayClient implements CoreGatewayClient { return this; } + setDeleteRuleResponse(response: DeleteGatewayRuleResponse): this { + this.deleteRuleResponse = response; + return this; + } + setError(error: Error | undefined): this { this.error = error; return this; @@ -908,6 +933,12 @@ export class TestGatewayClient implements CoreGatewayClient { ); } + async deleteGateway(id: string, options: CoreOptions): Promise { + this.calls.push({ method: "deleteGateway", args: [id, options] }); + if (this.error) throw this.error; + return this.deleteResponse; + } + async createGatewayTarget( input: CreateGatewayTargetInput, options: CoreOptions, @@ -963,6 +994,16 @@ export class TestGatewayClient implements CoreGatewayClient { ); } + async deleteGatewayTarget( + gatewayId: string, + targetId: string, + options: CoreOptions, + ): Promise { + this.calls.push({ method: "deleteGatewayTarget", args: [gatewayId, targetId, options] }); + if (this.error) throw this.error; + return this.deleteTargetResponse; + } + async createGatewayRule( input: CreateGatewayRuleInput, options: CoreOptions, @@ -1008,6 +1049,15 @@ export class TestGatewayClient implements CoreGatewayClient { DEFAULT_LIST_GATEWAY_RULES_RESPONSE ); } + async deleteGatewayRule( + gatewayId: string, + ruleId: string, + options: CoreOptions, + ): Promise { + this.calls.push({ method: "deleteGatewayRule", args: [gatewayId, ruleId, options] }); + if (this.error) throw this.error; + return this.deleteRuleResponse; + } } type TestCoreClientOptions = { From d543f8bd8db60bdc0220338104b02f07447725d4 Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Fri, 7 Aug 2026 17:41:24 +0000 Subject: [PATCH 2/4] feat(gateway): complete delete command surface --- src/core/gateway.delete.test.ts | 50 -- src/core/gateway.test.ts | 27 ++ src/core/gateway.tsx | 1 + ...DeleteGatewayCommand.37b84cbb4cb88a58.json | 4 + ...teGatewayRuleCommand.ff871fe95393b0ae.json | 4 + ...eGatewayTargetCommand.92e90d943461f68.json | 5 + ...eGatewayTargetCommand.caedb7e716656bf.json | 5 + ...tGatewayTargetCommand.92e90d943461f68.json | 34 ++ .../delete/connector-delete.golden.json | 5 + .../delete/gateway-delete.golden.json | 4 + .../__fixtures__/delete/resources.json | 7 + .../delete/rule-delete.golden.json | 4 + .../delete/target-delete.golden.json | 5 + .../gateway/connector/delete/index.tsx | 34 ++ src/handlers/gateway/connector/index.tsx | 4 +- src/handlers/gateway/gateway.delete.test.tsx | 454 +++++++++++++++++- src/handlers/gateway/gateway.test.tsx | 1 + 17 files changed, 593 insertions(+), 55 deletions(-) delete mode 100644 src/core/gateway.delete.test.ts create mode 100644 src/handlers/gateway/__fixtures__/delete/DeleteGatewayCommand.37b84cbb4cb88a58.json create mode 100644 src/handlers/gateway/__fixtures__/delete/DeleteGatewayRuleCommand.ff871fe95393b0ae.json create mode 100644 src/handlers/gateway/__fixtures__/delete/DeleteGatewayTargetCommand.92e90d943461f68.json create mode 100644 src/handlers/gateway/__fixtures__/delete/DeleteGatewayTargetCommand.caedb7e716656bf.json create mode 100644 src/handlers/gateway/__fixtures__/delete/GetGatewayTargetCommand.92e90d943461f68.json create mode 100644 src/handlers/gateway/__fixtures__/delete/connector-delete.golden.json create mode 100644 src/handlers/gateway/__fixtures__/delete/gateway-delete.golden.json create mode 100644 src/handlers/gateway/__fixtures__/delete/resources.json create mode 100644 src/handlers/gateway/__fixtures__/delete/rule-delete.golden.json create mode 100644 src/handlers/gateway/__fixtures__/delete/target-delete.golden.json create mode 100644 src/handlers/gateway/connector/delete/index.tsx diff --git a/src/core/gateway.delete.test.ts b/src/core/gateway.delete.test.ts deleted file mode 100644 index 1ba54d5d9..000000000 --- a/src/core/gateway.delete.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { expect, test } from "bun:test"; -import { - DeleteGatewayCommand, - DeleteGatewayRuleCommand, - DeleteGatewayTargetCommand, - type BedrockAgentCoreControlClient, -} from "@aws-sdk/client-bedrock-agentcore-control"; -import { GatewayClient } from "./gateway"; -import type { AwsClients } from "./types"; - -test("maps Gateway, Target, and Rule selectors to their delete commands", async () => { - const commands: unknown[] = []; - const control = { - send: async (command: unknown) => { - commands.push(command); - return {}; - }, - } as unknown as BedrockAgentCoreControlClient; - const clients: AwsClients = { - control: () => control, - data: () => { - throw new Error("unexpected data client"); - }, - iam: () => { - throw new Error("unexpected IAM client"); - }, - }; - const gateway = new GatewayClient(clients); - const options = { region: "us-west-2" }; - - await gateway.deleteGateway("gateway-1", options); - await gateway.deleteGatewayTarget("gateway-1", "target-1", options); - await gateway.deleteGatewayRule("gateway-1", "rule-1", options); - - expect(commands).toHaveLength(3); - expect(commands[0]).toBeInstanceOf(DeleteGatewayCommand); - expect((commands[0] as DeleteGatewayCommand).input).toEqual({ - gatewayIdentifier: "gateway-1", - }); - expect(commands[1]).toBeInstanceOf(DeleteGatewayTargetCommand); - expect((commands[1] as DeleteGatewayTargetCommand).input).toEqual({ - gatewayIdentifier: "gateway-1", - targetId: "target-1", - }); - expect(commands[2]).toBeInstanceOf(DeleteGatewayRuleCommand); - expect((commands[2] as DeleteGatewayRuleCommand).input).toEqual({ - gatewayIdentifier: "gateway-1", - ruleId: "rule-1", - }); -}); diff --git a/src/core/gateway.test.ts b/src/core/gateway.test.ts index 749f9cada..96fdd38d9 100644 --- a/src/core/gateway.test.ts +++ b/src/core/gateway.test.ts @@ -1,5 +1,8 @@ import { describe, expect, test } from "bun:test"; import { + DeleteGatewayCommand, + DeleteGatewayRuleCommand, + DeleteGatewayTargetCommand, GetGatewayCommand, GetGatewayTargetCommand, UpdateGatewayCommand, @@ -58,6 +61,30 @@ function target(): GetGatewayTargetResponse { } as unknown as GetGatewayTargetResponse; } +test("maps Gateway, Target, and Rule selectors to their delete commands", async () => { + const { client, commands } = gatewayClient([{}, {}, {}]); + + await client.deleteGateway("gateway-1", OPTIONS); + await client.deleteGatewayTarget("gateway-1", "target-1", OPTIONS); + await client.deleteGatewayRule("gateway-1", "rule-1", OPTIONS); + + expect(commands).toHaveLength(3); + expect(commands[0]).toBeInstanceOf(DeleteGatewayCommand); + expect((commands[0] as DeleteGatewayCommand).input).toEqual({ + gatewayIdentifier: "gateway-1", + }); + expect(commands[1]).toBeInstanceOf(DeleteGatewayTargetCommand); + expect((commands[1] as DeleteGatewayTargetCommand).input).toEqual({ + gatewayIdentifier: "gateway-1", + targetId: "target-1", + }); + expect(commands[2]).toBeInstanceOf(DeleteGatewayRuleCommand); + expect((commands[2] as DeleteGatewayRuleCommand).input).toEqual({ + gatewayIdentifier: "gateway-1", + ruleId: "rule-1", + }); +}); + function gatewayClient(responses: unknown[]): { client: GatewayClient; commands: unknown[]; diff --git a/src/core/gateway.tsx b/src/core/gateway.tsx index e5a60258e..4c3aa074a 100644 --- a/src/core/gateway.tsx +++ b/src/core/gateway.tsx @@ -261,6 +261,7 @@ export class GatewayClient implements CoreGatewayClient { ): Promise { return this.clients.control(toClientConfig(options)).send(new UpdateGatewayRuleCommand(input)); } + async deleteGatewayRule( gatewayId: string, ruleId: string, diff --git a/src/handlers/gateway/__fixtures__/delete/DeleteGatewayCommand.37b84cbb4cb88a58.json b/src/handlers/gateway/__fixtures__/delete/DeleteGatewayCommand.37b84cbb4cb88a58.json new file mode 100644 index 000000000..c855f1a72 --- /dev/null +++ b/src/handlers/gateway/__fixtures__/delete/DeleteGatewayCommand.37b84cbb4cb88a58.json @@ -0,0 +1,4 @@ +{ + "gatewayId": "agentcore-cli-gateway-delete-fixture-zdun4d5xtu", + "status": "DELETING" +} \ No newline at end of file diff --git a/src/handlers/gateway/__fixtures__/delete/DeleteGatewayRuleCommand.ff871fe95393b0ae.json b/src/handlers/gateway/__fixtures__/delete/DeleteGatewayRuleCommand.ff871fe95393b0ae.json new file mode 100644 index 000000000..80c363c7c --- /dev/null +++ b/src/handlers/gateway/__fixtures__/delete/DeleteGatewayRuleCommand.ff871fe95393b0ae.json @@ -0,0 +1,4 @@ +{ + "ruleId": "fe512cc7-644d-467f-a11c-13fad6fd657a", + "status": "DELETING" +} \ No newline at end of file diff --git a/src/handlers/gateway/__fixtures__/delete/DeleteGatewayTargetCommand.92e90d943461f68.json b/src/handlers/gateway/__fixtures__/delete/DeleteGatewayTargetCommand.92e90d943461f68.json new file mode 100644 index 000000000..9fb68cea9 --- /dev/null +++ b/src/handlers/gateway/__fixtures__/delete/DeleteGatewayTargetCommand.92e90d943461f68.json @@ -0,0 +1,5 @@ +{ + "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:603141041947:gateway/agentcore-cli-gateway-delete-fixture-zdun4d5xtu", + "targetId": "NHC2SGFFH8", + "status": "DELETING" +} \ No newline at end of file diff --git a/src/handlers/gateway/__fixtures__/delete/DeleteGatewayTargetCommand.caedb7e716656bf.json b/src/handlers/gateway/__fixtures__/delete/DeleteGatewayTargetCommand.caedb7e716656bf.json new file mode 100644 index 000000000..6c6eb5e7b --- /dev/null +++ b/src/handlers/gateway/__fixtures__/delete/DeleteGatewayTargetCommand.caedb7e716656bf.json @@ -0,0 +1,5 @@ +{ + "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:603141041947:gateway/agentcore-cli-gateway-delete-fixture-zdun4d5xtu", + "targetId": "H7D9WTSBL1", + "status": "DELETING" +} \ No newline at end of file diff --git a/src/handlers/gateway/__fixtures__/delete/GetGatewayTargetCommand.92e90d943461f68.json b/src/handlers/gateway/__fixtures__/delete/GetGatewayTargetCommand.92e90d943461f68.json new file mode 100644 index 000000000..16ee57591 --- /dev/null +++ b/src/handlers/gateway/__fixtures__/delete/GetGatewayTargetCommand.92e90d943461f68.json @@ -0,0 +1,34 @@ +{ + "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:603141041947:gateway/agentcore-cli-gateway-delete-fixture-zdun4d5xtu", + "targetId": "NHC2SGFFH8", + "createdAt": { + "$date": "2026-08-07T17:38:06.724Z" + }, + "updatedAt": { + "$date": "2026-08-07T17:38:07.733Z" + }, + "status": "READY", + "name": "web-search-delete-fixture", + "targetConfiguration": { + "mcp": { + "connector": { + "source": { + "connectorId": "web-search" + }, + "configurations": [ + { + "name": "WebSearch", + "parameterValues": { + "maxResults": 10 + } + } + ] + } + } + }, + "credentialProviderConfigurations": [ + { + "credentialProviderType": "GATEWAY_IAM_ROLE" + } + ] +} \ No newline at end of file diff --git a/src/handlers/gateway/__fixtures__/delete/connector-delete.golden.json b/src/handlers/gateway/__fixtures__/delete/connector-delete.golden.json new file mode 100644 index 000000000..9fb68cea9 --- /dev/null +++ b/src/handlers/gateway/__fixtures__/delete/connector-delete.golden.json @@ -0,0 +1,5 @@ +{ + "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:603141041947:gateway/agentcore-cli-gateway-delete-fixture-zdun4d5xtu", + "targetId": "NHC2SGFFH8", + "status": "DELETING" +} \ No newline at end of file diff --git a/src/handlers/gateway/__fixtures__/delete/gateway-delete.golden.json b/src/handlers/gateway/__fixtures__/delete/gateway-delete.golden.json new file mode 100644 index 000000000..c855f1a72 --- /dev/null +++ b/src/handlers/gateway/__fixtures__/delete/gateway-delete.golden.json @@ -0,0 +1,4 @@ +{ + "gatewayId": "agentcore-cli-gateway-delete-fixture-zdun4d5xtu", + "status": "DELETING" +} \ No newline at end of file diff --git a/src/handlers/gateway/__fixtures__/delete/resources.json b/src/handlers/gateway/__fixtures__/delete/resources.json new file mode 100644 index 000000000..075d25db5 --- /dev/null +++ b/src/handlers/gateway/__fixtures__/delete/resources.json @@ -0,0 +1,7 @@ +{ + "gatewayId": "agentcore-cli-gateway-delete-fixture-zdun4d5xtu", + "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:603141041947:gateway/agentcore-cli-gateway-delete-fixture-zdun4d5xtu", + "targetId": "H7D9WTSBL1", + "connectorId": "NHC2SGFFH8", + "ruleId": "fe512cc7-644d-467f-a11c-13fad6fd657a" +} diff --git a/src/handlers/gateway/__fixtures__/delete/rule-delete.golden.json b/src/handlers/gateway/__fixtures__/delete/rule-delete.golden.json new file mode 100644 index 000000000..80c363c7c --- /dev/null +++ b/src/handlers/gateway/__fixtures__/delete/rule-delete.golden.json @@ -0,0 +1,4 @@ +{ + "ruleId": "fe512cc7-644d-467f-a11c-13fad6fd657a", + "status": "DELETING" +} \ No newline at end of file diff --git a/src/handlers/gateway/__fixtures__/delete/target-delete.golden.json b/src/handlers/gateway/__fixtures__/delete/target-delete.golden.json new file mode 100644 index 000000000..6c6eb5e7b --- /dev/null +++ b/src/handlers/gateway/__fixtures__/delete/target-delete.golden.json @@ -0,0 +1,5 @@ +{ + "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:603141041947:gateway/agentcore-cli-gateway-delete-fixture-zdun4d5xtu", + "targetId": "H7D9WTSBL1", + "status": "DELETING" +} \ No newline at end of file diff --git a/src/handlers/gateway/connector/delete/index.tsx b/src/handlers/gateway/connector/delete/index.tsx new file mode 100644 index 000000000..52df6eba2 --- /dev/null +++ b/src/handlers/gateway/connector/delete/index.tsx @@ -0,0 +1,34 @@ +import z from "zod"; +import { InputValidationError } from "../../../../errors"; +import { createHandler, flag } from "../../../../router"; +import { JsonRendererKey } from "../../../../tui"; +import type { Core } from "../../../types"; +import { coreOptsFromCtx } from "../../../utils"; +import { GatewayConnectorTarget } from "../gatewayConnectorTarget"; + +export const createDeleteGatewayConnectorHandler = (core: Core) => + createHandler({ + name: "delete", + description: "delete a connector-backed Gateway Target", + flags: [ + flag("gateway-id", "the parent Gateway ID", z.string().optional()), + flag("id", "the connector-backed Gateway Target ID", z.string().optional()), + ], + handle: async (ctx, flags) => { + if (!flags["gateway-id"]) { + throw new InputValidationError("required option '--gateway-id ' not specified"); + } + if (!flags.id) { + throw new InputValidationError("required option '--id ' not specified"); + } + + const options = coreOptsFromCtx(ctx); + const target = await core.gateway.getGatewayTarget(flags["gateway-id"], flags.id, options); + if (!GatewayConnectorTarget.is(target.targetConfiguration)) { + throw new InputValidationError(`Gateway Target "${flags.id}" is not connector-backed`); + } + ctx + .require(JsonRendererKey) + .renderJson(await core.gateway.deleteGatewayTarget(flags["gateway-id"], flags.id, options)); + }, + }); diff --git a/src/handlers/gateway/connector/index.tsx b/src/handlers/gateway/connector/index.tsx index 188b3f263..587c05e69 100644 --- a/src/handlers/gateway/connector/index.tsx +++ b/src/handlers/gateway/connector/index.tsx @@ -3,6 +3,7 @@ import { Router } from "../../../router"; import { createHelpDefault } from "../../help"; import type { Core } from "../../types"; import { createCreateGatewayConnectorHandler } from "./create"; +import { createDeleteGatewayConnectorHandler } from "./delete"; import { createGetGatewayConnectorHandler } from "./get"; import { createListGatewayConnectorsHandler } from "./list"; import { createUpdateGatewayConnectorHandler } from "./update"; @@ -13,5 +14,6 @@ export function createGatewayConnectorHandler(core: Core, io: AppIO): Router { .handler(createCreateGatewayConnectorHandler(core, io)) .handler(createUpdateGatewayConnectorHandler(core, io)) .handler(createGetGatewayConnectorHandler(core)) - .handler(createListGatewayConnectorsHandler(core)); + .handler(createListGatewayConnectorsHandler(core)) + .handler(createDeleteGatewayConnectorHandler(core)); } diff --git a/src/handlers/gateway/gateway.delete.test.tsx b/src/handlers/gateway/gateway.delete.test.tsx index d70802af2..66c69d9b1 100644 --- a/src/handlers/gateway/gateway.delete.test.tsx +++ b/src/handlers/gateway/gateway.delete.test.tsx @@ -1,11 +1,34 @@ import { describe, expect, test } from "bun:test"; -import type { - DeleteGatewayResponse, - DeleteGatewayRuleResponse, - DeleteGatewayTargetResponse, +import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { + CreateGatewayCommand, + CreateGatewayRuleCommand, + CreateGatewayTargetCommand, + DeleteGatewayCommand, + DeleteGatewayRuleCommand, + DeleteGatewayTargetCommand, + type DeleteGatewayResponse, + type DeleteGatewayRuleResponse, + type DeleteGatewayTargetResponse, + GetGatewayCommand, + GetGatewayRuleCommand, + GetGatewayTargetCommand, + type GetGatewayTargetResponse, } from "@aws-sdk/client-bedrock-agentcore-control"; +import { + CreateRoleCommand, + DeleteRoleCommand, + DeleteRolePolicyCommand, + PutRolePolicyCommand, +} from "@aws-sdk/client-iam"; +import { CoreClient } from "../../core"; +import { createControlClient, createIamClient } from "../../core/factories"; import { createSilentLogger, + fixtureFactories, + isRecording, + matchGolden, TestCoreClient, TestGlobalConfigAccessor, testIO, @@ -68,6 +91,56 @@ describe("gateway delete commands", () => { expect(JSON.parse(result.stdout)).toEqual(response); }); + test("deletes a connector-backed Target", async () => { + const response = { targetId: TARGET_ID, status: "DELETING" } as DeleteGatewayTargetResponse; + const core = new TestCoreClient(); + core.gateway + .setGetTargetResponse({ + targetId: TARGET_ID, + targetConfiguration: { + mcp: { connector: { source: { connectorId: "web-search" } } }, + }, + } as GetGatewayTargetResponse) + .setDeleteTargetResponse(response); + + const result = await run( + ["gateway", "connector", "delete", "--gateway-id", GATEWAY_ID, "--id", TARGET_ID], + core, + ); + + expect(core.gateway.calls).toEqual([ + { + method: "getGatewayTarget", + args: [GATEWAY_ID, TARGET_ID, { region: REGION }], + }, + { + method: "deleteGatewayTarget", + args: [GATEWAY_ID, TARGET_ID, { region: REGION }], + }, + ]); + expect(JSON.parse(result.stdout)).toEqual(response); + }); + + test("rejects a non-connector Target without deleting it", async () => { + const core = new TestCoreClient(); + core.gateway.setGetTargetResponse({ + targetId: TARGET_ID, + targetConfiguration: { + http: { passthrough: { endpoint: "https://example.test", protocolType: "CUSTOM" } }, + }, + } as GetGatewayTargetResponse); + + await expect( + run(["gateway", "connector", "delete", "--gateway-id", GATEWAY_ID, "--id", TARGET_ID], core), + ).rejects.toThrow(/not connector-backed/); + expect(core.gateway.calls).toEqual([ + { + method: "getGatewayTarget", + args: [GATEWAY_ID, TARGET_ID, { region: REGION }], + }, + ]); + }); + test("deletes a Rule", async () => { const response = { ruleId: RULE_ID, status: "DELETING" } as DeleteGatewayRuleResponse; const core = new TestCoreClient(); @@ -93,6 +166,8 @@ describe("gateway delete validation", () => { ["Gateway selector", ["gateway", "delete"], /--id/], ["Target parent", ["gateway", "target", "delete"], /--gateway-id/], ["Target selector", ["gateway", "target", "delete", "--gateway-id", GATEWAY_ID], /--target-id/], + ["Connector parent", ["gateway", "connector", "delete"], /--gateway-id/], + ["Connector selector", ["gateway", "connector", "delete", "--gateway-id", GATEWAY_ID], /--id/], ["Rule parent", ["gateway", "rule", "delete"], /--gateway-id/], ["Rule selector", ["gateway", "rule", "delete", "--gateway-id", GATEWAY_ID], /--rule-id/], ] as const)("rejects a missing %s before calling Core", async (_name, args, error) => { @@ -102,3 +177,374 @@ describe("gateway delete validation", () => { expect(core.gateway.calls).toEqual([]); }); }); + +const FIXTURES = join(import.meta.dir, "__fixtures__", "delete"); +const RESOURCE_STATE = join(FIXTURES, "resources.json"); +const GATEWAY_NAME = "agentcore-cli-gateway-delete-fixture"; +const ROLE_NAME = "AgentCoreCliGatewayDeleteFixtureRole"; +const POLICY_NAME = "AgentCoreCliGatewayDeleteFixture"; +const HTTP_TARGET_NAME = "http-delete-fixture"; +const CONNECTOR_TARGET_NAME = "web-search-delete-fixture"; +const FLOW_TIMEOUT = 600_000; + +type FixtureState = { + gatewayId: string; + gatewayArn: string; + targetId: string; + connectorId: string; + ruleId: string; +}; + +function createFixtureCore(): CoreClient { + const { createControlClient, createDataClient, createIamClient } = fixtureFactories(FIXTURES); + return new CoreClient({ + createControlClient, + createDataClient, + createIamClient, + logger: createSilentLogger(), + }); +} + +async function runFixture(args: string[]): Promise { + const io = testIO(); + const root = createRootHandler(createFixtureCore(), { + io: io.io, + logger: createSilentLogger(), + globalConfigAccessor: new TestGlobalConfigAccessor(), + }); + await root.route(["node", "agentcore", ...args, "--region", "us-east-1"]); + return io.stdout(); +} + +class GatewayDeleteFixture { + private readonly control = createControlClient({ region: "us-east-1" }); + private readonly iam = createIamClient({ region: "us-east-1" }); + + async setup(): Promise { + await this.ignoreMissing(() => + this.iam.send(new DeleteRolePolicyCommand({ RoleName: ROLE_NAME, PolicyName: POLICY_NAME })), + ); + await this.ignoreMissing(() => this.iam.send(new DeleteRoleCommand({ RoleName: ROLE_NAME }))); + const role = await this.iam.send( + new CreateRoleCommand({ + RoleName: ROLE_NAME, + AssumeRolePolicyDocument: JSON.stringify({ + Version: "2012-10-17", + Statement: [ + { + Effect: "Allow", + Principal: { Service: "bedrock-agentcore.amazonaws.com" }, + Action: "sts:AssumeRole", + }, + ], + }), + }), + ); + if (!role.Role?.Arn) throw new Error("IAM did not return the fixture role ARN"); + await Bun.sleep(10_000); + + const gateway = await this.control.send( + new CreateGatewayCommand({ + name: GATEWAY_NAME, + roleArn: role.Role.Arn, + authorizerType: "NONE", + description: "Disposable Gateway Delete fixture", + }), + ); + if (!gateway.gatewayId || !gateway.gatewayArn) { + throw new Error("CreateGateway did not return fixture identifiers"); + } + await this.waitUntil( + () => this.control.send(new GetGatewayCommand({ gatewayIdentifier: gateway.gatewayId })), + (response) => response.status === "READY", + ); + + await this.iam.send( + new PutRolePolicyCommand({ + RoleName: ROLE_NAME, + PolicyName: POLICY_NAME, + PolicyDocument: JSON.stringify({ + Version: "2012-10-17", + Statement: [ + { + Effect: "Allow", + Action: "bedrock-agentcore:InvokeGateway", + Resource: gateway.gatewayArn, + }, + { + Effect: "Allow", + Action: "bedrock-agentcore:InvokeWebSearch", + Resource: "arn:aws:bedrock-agentcore:us-east-1:aws:tool/web-search.v1", + }, + ], + }), + }), + ); + + const target = await this.control.send( + new CreateGatewayTargetCommand({ + gatewayIdentifier: gateway.gatewayId, + name: HTTP_TARGET_NAME, + targetConfiguration: { + http: { + passthrough: { + endpoint: "https://example.com", + protocolType: "CUSTOM", + }, + }, + }, + }), + ); + const connector = await this.control.send( + new CreateGatewayTargetCommand({ + gatewayIdentifier: gateway.gatewayId, + name: CONNECTOR_TARGET_NAME, + targetConfiguration: { + mcp: { + connector: { + source: { connectorId: "web-search" }, + configurations: [ + { + name: "WebSearch", + parameterValues: { maxResults: 10 }, + }, + ], + }, + }, + }, + credentialProviderConfigurations: [{ credentialProviderType: "GATEWAY_IAM_ROLE" }], + }), + ); + if (!target.targetId || !connector.targetId) { + throw new Error("CreateGatewayTarget did not return fixture identifiers"); + } + await Promise.all( + [target.targetId, connector.targetId].map((targetId) => + this.waitUntil( + () => + this.control.send( + new GetGatewayTargetCommand({ + gatewayIdentifier: gateway.gatewayId, + targetId, + }), + ), + (response) => response.status === "READY", + ), + ), + ); + + const rule = await this.control.send( + new CreateGatewayRuleCommand({ + gatewayIdentifier: gateway.gatewayId, + priority: 10, + actions: [ + { + routeToTarget: { + staticRoute: { + targetName: HTTP_TARGET_NAME, + }, + }, + }, + ], + }), + ); + if (!rule.ruleId) throw new Error("CreateGatewayRule did not return the fixture rule ID"); + await this.waitUntil( + () => + this.control.send( + new GetGatewayRuleCommand({ + gatewayIdentifier: gateway.gatewayId, + ruleId: rule.ruleId, + }), + ), + (response) => response.status === "ACTIVE", + ); + + const state = { + gatewayId: gateway.gatewayId, + gatewayArn: gateway.gatewayArn, + targetId: target.targetId, + connectorId: connector.targetId, + ruleId: rule.ruleId, + }; + mkdirSync(FIXTURES, { recursive: true }); + writeFileSync(RESOURCE_STATE, `${JSON.stringify(state, null, 2)}\n`); + return state; + } + + async verifyMissing(operation: () => Promise): Promise { + if (!isRecording()) return; + await this.waitUntilMissing(operation); + } + + async cleanup(state: FixtureState): Promise { + await this.ignoreMissing(() => + this.control.send( + new DeleteGatewayRuleCommand({ + gatewayIdentifier: state.gatewayId, + ruleId: state.ruleId, + }), + ), + ); + await this.waitUntilMissing(() => + this.control.send( + new GetGatewayRuleCommand({ + gatewayIdentifier: state.gatewayId, + ruleId: state.ruleId, + }), + ), + ); + for (const targetId of [state.targetId, state.connectorId]) { + await this.ignoreMissing(() => + this.control.send( + new DeleteGatewayTargetCommand({ + gatewayIdentifier: state.gatewayId, + targetId, + }), + ), + ); + await this.waitUntilMissing(() => + this.control.send( + new GetGatewayTargetCommand({ + gatewayIdentifier: state.gatewayId, + targetId, + }), + ), + ); + } + await this.ignoreMissing(() => + this.control.send(new DeleteGatewayCommand({ gatewayIdentifier: state.gatewayId })), + ); + await this.waitUntilMissing(() => + this.control.send(new GetGatewayCommand({ gatewayIdentifier: state.gatewayId })), + ); + await this.ignoreMissing(() => + this.iam.send( + new DeleteRolePolicyCommand({ + RoleName: ROLE_NAME, + PolicyName: POLICY_NAME, + }), + ), + ); + await this.ignoreMissing(() => this.iam.send(new DeleteRoleCommand({ RoleName: ROLE_NAME }))); + } + + private async waitUntil( + operation: () => Promise, + done: (response: T) => boolean, + ): Promise { + for (let attempt = 0; attempt < 60; attempt += 1) { + const response = await operation(); + if (done(response)) return response; + await Bun.sleep(2_000); + } + throw new Error("Timed out waiting for fixture resource state"); + } + + private async waitUntilMissing(operation: () => Promise): Promise { + for (let attempt = 0; attempt < 60; attempt += 1) { + try { + await operation(); + } catch (error) { + if ((error as Error).name === "ResourceNotFoundException") return; + throw error; + } + await Bun.sleep(2_000); + } + throw new Error("Timed out waiting for fixture resource deletion"); + } + + private async ignoreMissing(operation: () => Promise): Promise { + try { + await operation(); + } catch (error) { + if (!["ResourceNotFoundException", "NoSuchEntityException"].includes((error as Error).name)) { + throw error; + } + } + } +} + +test( + "deletes a Rule, Target, Connector, and Gateway through the real Core", + async () => { + const fixture = new GatewayDeleteFixture(); + const state = isRecording() + ? await fixture.setup() + : (JSON.parse(readFileSync(RESOURCE_STATE, "utf8")) as FixtureState); + + try { + const ruleStdout = await runFixture([ + "gateway", + "rule", + "delete", + "--gateway-id", + state.gatewayId, + "--rule-id", + state.ruleId, + ]); + matchGolden(FIXTURES, "rule-delete.golden.json", ruleStdout); + expect(JSON.parse(ruleStdout).ruleId).toBe(state.ruleId); + await fixture.verifyMissing(() => + createControlClient({ region: "us-east-1" }).send( + new GetGatewayRuleCommand({ + gatewayIdentifier: state.gatewayId, + ruleId: state.ruleId, + }), + ), + ); + + const targetStdout = await runFixture([ + "gateway", + "target", + "delete", + "--gateway-id", + state.gatewayId, + "--target-id", + state.targetId, + ]); + matchGolden(FIXTURES, "target-delete.golden.json", targetStdout); + expect(JSON.parse(targetStdout).targetId).toBe(state.targetId); + await fixture.verifyMissing(() => + createControlClient({ region: "us-east-1" }).send( + new GetGatewayTargetCommand({ + gatewayIdentifier: state.gatewayId, + targetId: state.targetId, + }), + ), + ); + + const connectorStdout = await runFixture([ + "gateway", + "connector", + "delete", + "--gateway-id", + state.gatewayId, + "--id", + state.connectorId, + ]); + matchGolden(FIXTURES, "connector-delete.golden.json", connectorStdout); + expect(JSON.parse(connectorStdout).targetId).toBe(state.connectorId); + await fixture.verifyMissing(() => + createControlClient({ region: "us-east-1" }).send( + new GetGatewayTargetCommand({ + gatewayIdentifier: state.gatewayId, + targetId: state.connectorId, + }), + ), + ); + + const gatewayStdout = await runFixture(["gateway", "delete", "--id", state.gatewayId]); + matchGolden(FIXTURES, "gateway-delete.golden.json", gatewayStdout); + expect(JSON.parse(gatewayStdout).gatewayId).toBe(state.gatewayId); + await fixture.verifyMissing(() => + createControlClient({ region: "us-east-1" }).send( + new GetGatewayCommand({ gatewayIdentifier: state.gatewayId }), + ), + ); + } finally { + if (isRecording()) await fixture.cleanup(state); + } + }, + FLOW_TIMEOUT, +); diff --git a/src/handlers/gateway/gateway.test.tsx b/src/handlers/gateway/gateway.test.tsx index b46212e3f..22fb48711 100644 --- a/src/handlers/gateway/gateway.test.tsx +++ b/src/handlers/gateway/gateway.test.tsx @@ -93,6 +93,7 @@ describe("gateway command hierarchy", () => { "update", "get", "list", + "delete", ]); expect(rule?.children().map((child) => child.name())).toEqual([ "create", From 4f6b080582289d47f6d3ec11d2ba21b4ddb3295c Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Fri, 7 Aug 2026 17:56:38 +0000 Subject: [PATCH 3/4] test(gateway): record delete fixtures with e2e profile --- .../delete/DeleteGatewayCommand.37b84cbb4cb88a58.json | 4 ---- .../delete/DeleteGatewayCommand.e015bbd46d1859f1.json | 4 ++++ .../DeleteGatewayRuleCommand.cee854fd6fa9fd16.json | 4 ++++ .../DeleteGatewayRuleCommand.ff871fe95393b0ae.json | 4 ---- .../DeleteGatewayTargetCommand.92e90d943461f68.json | 5 ----- .../DeleteGatewayTargetCommand.94eb9a3262a6cda2.json | 5 +++++ .../DeleteGatewayTargetCommand.afcce9d3abeac495.json | 5 +++++ .../DeleteGatewayTargetCommand.caedb7e716656bf.json | 5 ----- ...n => GetGatewayTargetCommand.94eb9a3262a6cda2.json} | 8 ++++---- .../__fixtures__/delete/connector-delete.golden.json | 4 ++-- .../__fixtures__/delete/gateway-delete.golden.json | 2 +- .../gateway/__fixtures__/delete/resources.json | 10 +++++----- .../__fixtures__/delete/rule-delete.golden.json | 2 +- .../__fixtures__/delete/target-delete.golden.json | 4 ++-- 14 files changed, 33 insertions(+), 33 deletions(-) delete mode 100644 src/handlers/gateway/__fixtures__/delete/DeleteGatewayCommand.37b84cbb4cb88a58.json create mode 100644 src/handlers/gateway/__fixtures__/delete/DeleteGatewayCommand.e015bbd46d1859f1.json create mode 100644 src/handlers/gateway/__fixtures__/delete/DeleteGatewayRuleCommand.cee854fd6fa9fd16.json delete mode 100644 src/handlers/gateway/__fixtures__/delete/DeleteGatewayRuleCommand.ff871fe95393b0ae.json delete mode 100644 src/handlers/gateway/__fixtures__/delete/DeleteGatewayTargetCommand.92e90d943461f68.json create mode 100644 src/handlers/gateway/__fixtures__/delete/DeleteGatewayTargetCommand.94eb9a3262a6cda2.json create mode 100644 src/handlers/gateway/__fixtures__/delete/DeleteGatewayTargetCommand.afcce9d3abeac495.json delete mode 100644 src/handlers/gateway/__fixtures__/delete/DeleteGatewayTargetCommand.caedb7e716656bf.json rename src/handlers/gateway/__fixtures__/delete/{GetGatewayTargetCommand.92e90d943461f68.json => GetGatewayTargetCommand.94eb9a3262a6cda2.json} (69%) diff --git a/src/handlers/gateway/__fixtures__/delete/DeleteGatewayCommand.37b84cbb4cb88a58.json b/src/handlers/gateway/__fixtures__/delete/DeleteGatewayCommand.37b84cbb4cb88a58.json deleted file mode 100644 index c855f1a72..000000000 --- a/src/handlers/gateway/__fixtures__/delete/DeleteGatewayCommand.37b84cbb4cb88a58.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "gatewayId": "agentcore-cli-gateway-delete-fixture-zdun4d5xtu", - "status": "DELETING" -} \ No newline at end of file diff --git a/src/handlers/gateway/__fixtures__/delete/DeleteGatewayCommand.e015bbd46d1859f1.json b/src/handlers/gateway/__fixtures__/delete/DeleteGatewayCommand.e015bbd46d1859f1.json new file mode 100644 index 000000000..a7fa04545 --- /dev/null +++ b/src/handlers/gateway/__fixtures__/delete/DeleteGatewayCommand.e015bbd46d1859f1.json @@ -0,0 +1,4 @@ +{ + "gatewayId": "agentcore-cli-gateway-delete-fixture-oiemu02wfc", + "status": "DELETING" +} \ No newline at end of file diff --git a/src/handlers/gateway/__fixtures__/delete/DeleteGatewayRuleCommand.cee854fd6fa9fd16.json b/src/handlers/gateway/__fixtures__/delete/DeleteGatewayRuleCommand.cee854fd6fa9fd16.json new file mode 100644 index 000000000..1b1f7d63b --- /dev/null +++ b/src/handlers/gateway/__fixtures__/delete/DeleteGatewayRuleCommand.cee854fd6fa9fd16.json @@ -0,0 +1,4 @@ +{ + "ruleId": "80e073e4-eb05-4371-8e6a-f4bde522699c", + "status": "DELETING" +} \ No newline at end of file diff --git a/src/handlers/gateway/__fixtures__/delete/DeleteGatewayRuleCommand.ff871fe95393b0ae.json b/src/handlers/gateway/__fixtures__/delete/DeleteGatewayRuleCommand.ff871fe95393b0ae.json deleted file mode 100644 index 80c363c7c..000000000 --- a/src/handlers/gateway/__fixtures__/delete/DeleteGatewayRuleCommand.ff871fe95393b0ae.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "ruleId": "fe512cc7-644d-467f-a11c-13fad6fd657a", - "status": "DELETING" -} \ No newline at end of file diff --git a/src/handlers/gateway/__fixtures__/delete/DeleteGatewayTargetCommand.92e90d943461f68.json b/src/handlers/gateway/__fixtures__/delete/DeleteGatewayTargetCommand.92e90d943461f68.json deleted file mode 100644 index 9fb68cea9..000000000 --- a/src/handlers/gateway/__fixtures__/delete/DeleteGatewayTargetCommand.92e90d943461f68.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:603141041947:gateway/agentcore-cli-gateway-delete-fixture-zdun4d5xtu", - "targetId": "NHC2SGFFH8", - "status": "DELETING" -} \ No newline at end of file diff --git a/src/handlers/gateway/__fixtures__/delete/DeleteGatewayTargetCommand.94eb9a3262a6cda2.json b/src/handlers/gateway/__fixtures__/delete/DeleteGatewayTargetCommand.94eb9a3262a6cda2.json new file mode 100644 index 000000000..1cbd45898 --- /dev/null +++ b/src/handlers/gateway/__fixtures__/delete/DeleteGatewayTargetCommand.94eb9a3262a6cda2.json @@ -0,0 +1,5 @@ +{ + "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:685197708687:gateway/agentcore-cli-gateway-delete-fixture-oiemu02wfc", + "targetId": "U9OM2R9I8Q", + "status": "DELETING" +} \ No newline at end of file diff --git a/src/handlers/gateway/__fixtures__/delete/DeleteGatewayTargetCommand.afcce9d3abeac495.json b/src/handlers/gateway/__fixtures__/delete/DeleteGatewayTargetCommand.afcce9d3abeac495.json new file mode 100644 index 000000000..deb34f170 --- /dev/null +++ b/src/handlers/gateway/__fixtures__/delete/DeleteGatewayTargetCommand.afcce9d3abeac495.json @@ -0,0 +1,5 @@ +{ + "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:685197708687:gateway/agentcore-cli-gateway-delete-fixture-oiemu02wfc", + "targetId": "JYNNGDZ42F", + "status": "DELETING" +} \ No newline at end of file diff --git a/src/handlers/gateway/__fixtures__/delete/DeleteGatewayTargetCommand.caedb7e716656bf.json b/src/handlers/gateway/__fixtures__/delete/DeleteGatewayTargetCommand.caedb7e716656bf.json deleted file mode 100644 index 6c6eb5e7b..000000000 --- a/src/handlers/gateway/__fixtures__/delete/DeleteGatewayTargetCommand.caedb7e716656bf.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:603141041947:gateway/agentcore-cli-gateway-delete-fixture-zdun4d5xtu", - "targetId": "H7D9WTSBL1", - "status": "DELETING" -} \ No newline at end of file diff --git a/src/handlers/gateway/__fixtures__/delete/GetGatewayTargetCommand.92e90d943461f68.json b/src/handlers/gateway/__fixtures__/delete/GetGatewayTargetCommand.94eb9a3262a6cda2.json similarity index 69% rename from src/handlers/gateway/__fixtures__/delete/GetGatewayTargetCommand.92e90d943461f68.json rename to src/handlers/gateway/__fixtures__/delete/GetGatewayTargetCommand.94eb9a3262a6cda2.json index 16ee57591..b824529b7 100644 --- a/src/handlers/gateway/__fixtures__/delete/GetGatewayTargetCommand.92e90d943461f68.json +++ b/src/handlers/gateway/__fixtures__/delete/GetGatewayTargetCommand.94eb9a3262a6cda2.json @@ -1,11 +1,11 @@ { - "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:603141041947:gateway/agentcore-cli-gateway-delete-fixture-zdun4d5xtu", - "targetId": "NHC2SGFFH8", + "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:685197708687:gateway/agentcore-cli-gateway-delete-fixture-oiemu02wfc", + "targetId": "U9OM2R9I8Q", "createdAt": { - "$date": "2026-08-07T17:38:06.724Z" + "$date": "2026-08-07T17:54:45.522Z" }, "updatedAt": { - "$date": "2026-08-07T17:38:07.733Z" + "$date": "2026-08-07T17:54:46.453Z" }, "status": "READY", "name": "web-search-delete-fixture", diff --git a/src/handlers/gateway/__fixtures__/delete/connector-delete.golden.json b/src/handlers/gateway/__fixtures__/delete/connector-delete.golden.json index 9fb68cea9..1cbd45898 100644 --- a/src/handlers/gateway/__fixtures__/delete/connector-delete.golden.json +++ b/src/handlers/gateway/__fixtures__/delete/connector-delete.golden.json @@ -1,5 +1,5 @@ { - "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:603141041947:gateway/agentcore-cli-gateway-delete-fixture-zdun4d5xtu", - "targetId": "NHC2SGFFH8", + "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:685197708687:gateway/agentcore-cli-gateway-delete-fixture-oiemu02wfc", + "targetId": "U9OM2R9I8Q", "status": "DELETING" } \ No newline at end of file diff --git a/src/handlers/gateway/__fixtures__/delete/gateway-delete.golden.json b/src/handlers/gateway/__fixtures__/delete/gateway-delete.golden.json index c855f1a72..a7fa04545 100644 --- a/src/handlers/gateway/__fixtures__/delete/gateway-delete.golden.json +++ b/src/handlers/gateway/__fixtures__/delete/gateway-delete.golden.json @@ -1,4 +1,4 @@ { - "gatewayId": "agentcore-cli-gateway-delete-fixture-zdun4d5xtu", + "gatewayId": "agentcore-cli-gateway-delete-fixture-oiemu02wfc", "status": "DELETING" } \ No newline at end of file diff --git a/src/handlers/gateway/__fixtures__/delete/resources.json b/src/handlers/gateway/__fixtures__/delete/resources.json index 075d25db5..fa612366a 100644 --- a/src/handlers/gateway/__fixtures__/delete/resources.json +++ b/src/handlers/gateway/__fixtures__/delete/resources.json @@ -1,7 +1,7 @@ { - "gatewayId": "agentcore-cli-gateway-delete-fixture-zdun4d5xtu", - "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:603141041947:gateway/agentcore-cli-gateway-delete-fixture-zdun4d5xtu", - "targetId": "H7D9WTSBL1", - "connectorId": "NHC2SGFFH8", - "ruleId": "fe512cc7-644d-467f-a11c-13fad6fd657a" + "gatewayId": "agentcore-cli-gateway-delete-fixture-oiemu02wfc", + "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:685197708687:gateway/agentcore-cli-gateway-delete-fixture-oiemu02wfc", + "targetId": "JYNNGDZ42F", + "connectorId": "U9OM2R9I8Q", + "ruleId": "80e073e4-eb05-4371-8e6a-f4bde522699c" } diff --git a/src/handlers/gateway/__fixtures__/delete/rule-delete.golden.json b/src/handlers/gateway/__fixtures__/delete/rule-delete.golden.json index 80c363c7c..1b1f7d63b 100644 --- a/src/handlers/gateway/__fixtures__/delete/rule-delete.golden.json +++ b/src/handlers/gateway/__fixtures__/delete/rule-delete.golden.json @@ -1,4 +1,4 @@ { - "ruleId": "fe512cc7-644d-467f-a11c-13fad6fd657a", + "ruleId": "80e073e4-eb05-4371-8e6a-f4bde522699c", "status": "DELETING" } \ No newline at end of file diff --git a/src/handlers/gateway/__fixtures__/delete/target-delete.golden.json b/src/handlers/gateway/__fixtures__/delete/target-delete.golden.json index 6c6eb5e7b..deb34f170 100644 --- a/src/handlers/gateway/__fixtures__/delete/target-delete.golden.json +++ b/src/handlers/gateway/__fixtures__/delete/target-delete.golden.json @@ -1,5 +1,5 @@ { - "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:603141041947:gateway/agentcore-cli-gateway-delete-fixture-zdun4d5xtu", - "targetId": "H7D9WTSBL1", + "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:685197708687:gateway/agentcore-cli-gateway-delete-fixture-oiemu02wfc", + "targetId": "JYNNGDZ42F", "status": "DELETING" } \ No newline at end of file From c23c8942e20abc0fbc18d81649d63c0851854451 Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Fri, 7 Aug 2026 19:26:41 +0000 Subject: [PATCH 4/4] test(gateway): clean up partial delete fixtures --- src/handlers/gateway/gateway.delete.test.tsx | 96 ++++++++++++-------- 1 file changed, 59 insertions(+), 37 deletions(-) diff --git a/src/handlers/gateway/gateway.delete.test.tsx b/src/handlers/gateway/gateway.delete.test.tsx index 66c69d9b1..e25f47e0f 100644 --- a/src/handlers/gateway/gateway.delete.test.tsx +++ b/src/handlers/gateway/gateway.delete.test.tsx @@ -195,6 +195,12 @@ type FixtureState = { ruleId: string; }; +type FixtureResources = { + gatewayId?: string; + targetIds: string[]; + ruleId?: string; +}; + function createFixtureCore(): CoreClient { const { createControlClient, createDataClient, createIamClient } = fixtureFactories(FIXTURES); return new CoreClient({ @@ -220,7 +226,7 @@ class GatewayDeleteFixture { private readonly control = createControlClient({ region: "us-east-1" }); private readonly iam = createIamClient({ region: "us-east-1" }); - async setup(): Promise { + async setup(resources: FixtureResources): Promise { await this.ignoreMissing(() => this.iam.send(new DeleteRolePolicyCommand({ RoleName: ROLE_NAME, PolicyName: POLICY_NAME })), ); @@ -254,6 +260,7 @@ class GatewayDeleteFixture { if (!gateway.gatewayId || !gateway.gatewayArn) { throw new Error("CreateGateway did not return fixture identifiers"); } + resources.gatewayId = gateway.gatewayId; await this.waitUntil( () => this.control.send(new GetGatewayCommand({ gatewayIdentifier: gateway.gatewayId })), (response) => response.status === "READY", @@ -295,6 +302,11 @@ class GatewayDeleteFixture { }, }), ); + if (!target.targetId) { + throw new Error("CreateGatewayTarget did not return the fixture Target ID"); + } + resources.targetIds.push(target.targetId); + const connector = await this.control.send( new CreateGatewayTargetCommand({ gatewayIdentifier: gateway.gatewayId, @@ -315,9 +327,10 @@ class GatewayDeleteFixture { credentialProviderConfigurations: [{ credentialProviderType: "GATEWAY_IAM_ROLE" }], }), ); - if (!target.targetId || !connector.targetId) { - throw new Error("CreateGatewayTarget did not return fixture identifiers"); + if (!connector.targetId) { + throw new Error("CreateGatewayTarget did not return the fixture Connector ID"); } + resources.targetIds.push(connector.targetId); await Promise.all( [target.targetId, connector.targetId].map((targetId) => this.waitUntil( @@ -349,6 +362,7 @@ class GatewayDeleteFixture { }), ); if (!rule.ruleId) throw new Error("CreateGatewayRule did not return the fixture rule ID"); + resources.ruleId = rule.ruleId; await this.waitUntil( () => this.control.send( @@ -377,47 +391,53 @@ class GatewayDeleteFixture { await this.waitUntilMissing(operation); } - async cleanup(state: FixtureState): Promise { - await this.ignoreMissing(() => - this.control.send( - new DeleteGatewayRuleCommand({ - gatewayIdentifier: state.gatewayId, - ruleId: state.ruleId, - }), - ), - ); - await this.waitUntilMissing(() => - this.control.send( - new GetGatewayRuleCommand({ - gatewayIdentifier: state.gatewayId, - ruleId: state.ruleId, - }), - ), - ); - for (const targetId of [state.targetId, state.connectorId]) { + async cleanup(resources: FixtureResources): Promise { + if (resources.gatewayId && resources.ruleId) { await this.ignoreMissing(() => this.control.send( - new DeleteGatewayTargetCommand({ - gatewayIdentifier: state.gatewayId, - targetId, + new DeleteGatewayRuleCommand({ + gatewayIdentifier: resources.gatewayId, + ruleId: resources.ruleId, }), ), ); await this.waitUntilMissing(() => this.control.send( - new GetGatewayTargetCommand({ - gatewayIdentifier: state.gatewayId, - targetId, + new GetGatewayRuleCommand({ + gatewayIdentifier: resources.gatewayId, + ruleId: resources.ruleId, }), ), ); } - await this.ignoreMissing(() => - this.control.send(new DeleteGatewayCommand({ gatewayIdentifier: state.gatewayId })), - ); - await this.waitUntilMissing(() => - this.control.send(new GetGatewayCommand({ gatewayIdentifier: state.gatewayId })), - ); + + if (resources.gatewayId) { + for (const targetId of resources.targetIds) { + await this.ignoreMissing(() => + this.control.send( + new DeleteGatewayTargetCommand({ + gatewayIdentifier: resources.gatewayId, + targetId, + }), + ), + ); + await this.waitUntilMissing(() => + this.control.send( + new GetGatewayTargetCommand({ + gatewayIdentifier: resources.gatewayId, + targetId, + }), + ), + ); + } + await this.ignoreMissing(() => + this.control.send(new DeleteGatewayCommand({ gatewayIdentifier: resources.gatewayId })), + ); + await this.waitUntilMissing(() => + this.control.send(new GetGatewayCommand({ gatewayIdentifier: resources.gatewayId })), + ); + } + await this.ignoreMissing(() => this.iam.send( new DeleteRolePolicyCommand({ @@ -469,11 +489,13 @@ test( "deletes a Rule, Target, Connector, and Gateway through the real Core", async () => { const fixture = new GatewayDeleteFixture(); - const state = isRecording() - ? await fixture.setup() - : (JSON.parse(readFileSync(RESOURCE_STATE, "utf8")) as FixtureState); + const resources: FixtureResources = { targetIds: [] }; try { + const state = isRecording() + ? await fixture.setup(resources) + : (JSON.parse(readFileSync(RESOURCE_STATE, "utf8")) as FixtureState); + const ruleStdout = await runFixture([ "gateway", "rule", @@ -543,7 +565,7 @@ test( ), ); } finally { - if (isRecording()) await fixture.cleanup(state); + if (isRecording()) await fixture.cleanup(resources); } }, FLOW_TIMEOUT,