1import * as AWS from "@/AWS";
2import { Gateway } from "@/AWS/BedrockAgentCore";
3import { Role } from "@/AWS/IAM/Role.ts";
4import * as Test from "@/Test/Alchemy";
5import * as control from "@distilled.cloud/aws/bedrock-agentcore-control";
6import { expect } from "alchemy-test";
7import * as Effect from "effect/Effect";
8import * as Redacted from "effect/Redacted";
9import * as Schedule from "effect/Schedule";
10
11const { test } = Test.make({ providers: AWS.providers() });
12
13// Ungated typed-error probe.
14test.provider(
15 "getGateway on a nonexistent id fails with ResourceNotFoundException",
16 () =>
17 Effect.gen(function* () {
18 const error = yield* Effect.flip(
19 control.getGateway({
20 gatewayIdentifier: "alchemy-nonexistent-probe-0000000000",
21 }),
22 );
23 expect(error._tag).toBe("ResourceNotFoundException");
24 }),
25 { tags: ["provider:aws", "provider:aws:bedrockagentcore", "live"] },
26);
27
28const assertGatewayGone = (gatewayIdentifier: string) =>
29 Effect.gen(function* () {
30 const status = yield* control.getGateway({ gatewayIdentifier }).pipe(
31 Effect.map((r) => r.status as string),
32 Effect.catchTag("ResourceNotFoundException", () =>
33 Effect.succeed("GONE" as string),
34 ),
35 );
36 if (status !== "GONE") {
37 return yield* Effect.fail(
38 new Error(`gateway still exists (status: ${status})`),
39 );
40 }
41 }).pipe(
42 Effect.retry({
43 schedule: Schedule.max([
44 Schedule.fixed("5 seconds"),
45 Schedule.recurs(12),
46 ]),
47 }),
48 );
49
50const gatewayStack = (description: string) =>
51 Effect.gen(function* () {
52 const role = yield* Role("GatewayRole", {
53 assumeRolePolicyDocument: {
54 Version: "2012-10-17",
55 Statement: [
56 {
57 Effect: "Allow",
58 Principal: { Service: "bedrock-agentcore.amazonaws.com" },
59 Action: ["sts:AssumeRole"],
60 },
61 ],
62 },
63 });
64 const gateway = yield* Gateway("McpGateway", {
65 description,
66 roleArn: role.roleArn,
67 authorizerType: "AWS_IAM",
68 tags: { fixture: "agentcore-gateway" },
69 });
70 return { gateway };
71 });
72
73test.provider(
74 "create IAM-authorized MCP gateway, verify, update description, destroy",
75 (stack) =>
76 Effect.gen(function* () {
77 yield* stack.destroy();
78
79 const { gateway } = yield* stack.deploy(gatewayStack("initial"));
80
81 expect(gateway.gatewayId).toBeTruthy();
82 expect(gateway.gatewayArn).toContain(":gateway/");
83 expect(gateway.status).toBe("READY");
84 expect(gateway.gatewayUrl).toBeTruthy();
85
86 // out-of-band verification via distilled
87 const observed = yield* control.getGateway({
88 gatewayIdentifier: gateway.gatewayId,
89 });
90 expect(observed.status).toBe("READY");
91 expect(observed.authorizerType).toBe("AWS_IAM");
92 expect(observed.protocolType).toBe("MCP");
93
94 // update in place — same gateway id, new description
95 const { gateway: updated } = yield* stack.deploy(gatewayStack("updated"));
96 expect(updated.gatewayId).toBe(gateway.gatewayId);
97 const observedUpdated = yield* control.getGateway({
98 gatewayIdentifier: gateway.gatewayId,
99 });
100 const description = observedUpdated.description;
101 expect(
102 description !== undefined && Redacted.isRedacted(description)
103 ? Redacted.value(description)
104 : description,
105 ).toBe("updated");
106
107 yield* stack.destroy();
108 yield* assertGatewayGone(gateway.gatewayId);
109 }),
110 {
111 tags: [
112 "provider:aws",
113 "provider:aws:bedrockagentcore",
114 "provider:aws:iam",
115 "live",
116 ],
117 timeout: 240_000,
118 },
119);