1import * as AWS from "@/AWS";
2import { VpcLink } from "@/AWS/ApiGatewayV2";
3import * as Provider from "@/Provider";
4import * as Test from "@/Test/Alchemy";
5import * as agw2 from "@distilled.cloud/aws/apigatewayv2";
6import * as EC2 from "@distilled.cloud/aws/ec2";
7import { expect } from "alchemy-test";
8import * as Effect from "effect/Effect";
9import { getDefaultVpc } from "../DefaultVpc";
10
11const { test } = Test.make({ providers: AWS.providers() });
12
13// Ungated live probe: `list()` enumerates every v2 VPC link and maps each
14// item to the `read` Attributes shape.
15test.provider.skipIf(!!process.env.FAST)(
16 "list returns the account/region VPC links",
17 () =>
18 Effect.gen(function* () {
19 const provider = yield* Provider.findProvider(VpcLink);
20 const all = yield* provider.list();
21
22 expect(Array.isArray(all)).toBe(true);
23 for (const link of all) {
24 expect(typeof link.vpcLinkId).toBe("string");
25 expect(Array.isArray(link.subnetIds)).toBe(true);
26 }
27 }),
28 { tags: ["provider:aws", "provider:aws:apigatewayv2", "live"] },
29);
30
31// Full lifecycle. SKIPPED by default: a v2 VPC link takes ~1-2 minutes to
32// reach AVAILABLE (and a comparable window to release its ENIs on delete),
33// which exceeds the suite's provisioning-wait budget. Set
34// AWS_TEST_APIGWV2_VPCLINK=1 to run it against the default VPC's subnets.
35test.provider.skipIf(
36 !!process.env.FAST || !process.env.AWS_TEST_APIGWV2_VPCLINK,
37)(
38 "create, rename, delete VPC link",
39 (stack) =>
40 Effect.gen(function* () {
41 yield* stack.destroy();
42
43 const { vpcId } = yield* getDefaultVpc;
44 const described = yield* EC2.describeSubnets({
45 Filters: [{ Name: "vpc-id", Values: [vpcId] }],
46 });
47 const subnets = (described.Subnets ?? [])
48 .map((subnet) => subnet.SubnetId)
49 .filter((id): id is string => id != null)
50 .slice(0, 2);
51 expect(subnets.length).toBeGreaterThan(0);
52
53 const deployLink = (description: string) =>
54 stack.deploy(
55 Effect.gen(function* () {
56 return yield* AWS.ApiGatewayV2.VpcLink("V2Link", {
57 name: `alchemy-v2link-${description}`,
58 subnetIds: subnets,
59 });
60 }),
61 );
62
63 const link = yield* deployLink("v1");
64 expect(link.vpcLinkId).toBeTruthy();
65
66 const remote = yield* agw2.getVpcLink({ VpcLinkId: link.vpcLinkId });
67 expect(remote.Name).toBe("alchemy-v2link-v1");
68 expect([...(remote.SubnetIds ?? [])].sort()).toEqual([...subnets].sort());
69
70 // Rename in place (name is the only mutable prop).
71 const renamed = yield* deployLink("v2");
72 expect(renamed.vpcLinkId).toBe(link.vpcLinkId);
73 const afterRename = yield* agw2.getVpcLink({
74 VpcLinkId: link.vpcLinkId,
75 });
76 expect(afterRename.Name).toBe("alchemy-v2link-v2");
77
78 yield* stack.destroy();
79
80 const gone = yield* agw2.getVpcLink({ VpcLinkId: link.vpcLinkId }).pipe(
81 // DELETING still resolves; the link is fully gone on NotFound.
82 Effect.map((r) => r.VpcLinkStatus ?? "unknown"),
83 Effect.catchTag("NotFoundException", () =>
84 Effect.succeed("deleted" as const),
85 ),
86 );
87 expect(["deleted", "DELETING"]).toContain(gone);
88 }),
89 {
90 tags: [
91 "provider:aws",
92 "provider:aws:apigatewayv2",
93 "provider:aws:ec2",
94 "live",
95 ],
96 timeout: 480_000,
97 },
98);