t3-code-android-nightly/.repos/alchemy-effect/packages/alchemy/test/AWS/EC2/ClientVpnRoute.test.ts
Julius Marminge 6f9cea00ae
chore(refs): sync Effect and Alchemy references to 4.0.1 and beta.80 (#16170)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 13:22:30 -07:00

270 lines
9.8 KiB
TypeScript

import * as AWS from "@/AWS";
import { ClientVpnEndpoint } from "@/AWS/EC2/ClientVpnEndpoint.ts";
import {
ClientVpnRoute,
type ClientVpnRouteProps,
} from "@/AWS/EC2/ClientVpnRoute.ts";
import { ClientVpnTargetNetworkAssociation } from "@/AWS/EC2/ClientVpnTargetNetworkAssociation.ts";
import * as Alchemy from "@/index.ts";
import * as Provider from "@/Provider";
import * as Test from "@/Test/Alchemy";
import * as ec2 from "@distilled.cloud/aws/ec2";
import { describe, expect } from "alchemy-test";
import * as Effect from "effect/Effect";
import {
assertClientVpnCertificateDeleted,
assertClientVpnRouteDeleted,
clientVpnAvailabilityZones,
clientVpnEndpointProps,
clientVpnTestTimeout,
clientVpnNetwork,
importClientVpnCertificate,
readClientVpnRoutes,
readClientVpnTargetNetworks,
waitForClientVpn,
} from "./fixtures/client-vpn.ts";
describe(
"Client VPN routes",
{ tags: ["provider:aws", "provider:aws:acm", "provider:aws:ec2", "live"] },
() => {
const { test, beforeAll, afterAll, deploy, destroy } = Test.make({
providers: AWS.providers(),
});
const certificate = beforeAll(
importClientVpnCertificate("ClientVpnRoutePrerequisites"),
);
const zones = beforeAll(clientVpnAvailabilityZones);
const Stack = Alchemy.Stack(
"ClientVpnRoutePrerequisites",
{ providers: AWS.providers(), state: Alchemy.localState() },
Effect.gen(function* () {
const certificateArn = yield* certificate;
const network = yield* clientVpnNetwork(yield* zones);
const endpoint = yield* ClientVpnEndpoint("Endpoint", {
...clientVpnEndpointProps(certificateArn),
vpcId: network.vpc.vpcId,
splitTunnel: true,
});
const firstAssociation = yield* ClientVpnTargetNetworkAssociation(
"FirstAssociation",
{
clientVpnEndpointId: endpoint.clientVpnEndpointId,
subnetId: network.firstSubnet.subnetId,
},
);
const secondAssociation = yield* ClientVpnTargetNetworkAssociation(
"SecondAssociation",
{
clientVpnEndpointId: endpoint.clientVpnEndpointId,
subnetId: network.secondSubnet.subnetId,
},
);
return { ...network, endpoint, firstAssociation, secondAssociation };
}),
);
const prerequisites = beforeAll(deploy(Stack), {
timeout: clientVpnTestTimeout,
});
afterAll(
destroy(Stack).pipe(
Effect.andThen(
certificate.pipe(Effect.flatMap(assertClientVpnCertificateDeleted)),
),
),
{ timeout: clientVpnTestTimeout },
);
test.provider(
"creates, lists, replaces, repairs, and deletes Client VPN routes",
(stack) =>
Effect.gen(function* () {
yield* stack.destroy();
const { endpoint, firstSubnet, secondSubnet } = yield* prerequisites;
const clientVpnEndpointId = endpoint.clientVpnEndpointId;
yield* waitForClientVpn(
readClientVpnTargetNetworks(clientVpnEndpointId),
(networks) =>
[firstSubnet.subnetId, secondSubnet.subnetId].every((subnetId) =>
networks.some(
(network) =>
network.TargetNetworkId === subnetId &&
network.Status?.Code === "associated",
),
),
"route target networks",
);
const deployRoute = (
destinationCidrBlock: string,
targetVpcSubnetId: ClientVpnRouteProps["targetVpcSubnetId"],
description?: string,
) =>
stack.deploy(
ClientVpnRoute("Route", {
clientVpnEndpointId,
destinationCidrBlock,
targetVpcSubnetId,
description,
}),
);
const created = yield* deployRoute(
"192.168.10.0/24",
firstSubnet.subnetId,
"Initial route",
);
expect(created.clientVpnEndpointId).toBe(clientVpnEndpointId);
expect(created.destinationCidrBlock).toBe("192.168.10.0/24");
expect(created.targetVpcSubnetId).toBe(firstSubnet.subnetId);
const routes = yield* waitForClientVpn(
readClientVpnRoutes(clientVpnEndpointId),
(items) =>
items.some(
(route) =>
route.DestinationCidr === "192.168.10.0/24" &&
route.TargetSubnet === firstSubnet.subnetId &&
route.Status?.Code === "active",
),
"active custom route",
);
expect(routes).toContainEqual(
expect.objectContaining({
DestinationCidr: "192.168.10.0/24",
TargetSubnet: firstSubnet.subnetId,
Description: "Initial route",
}),
);
const provider = yield* Provider.findProvider(ClientVpnRoute);
const listed = yield* waitForClientVpn(
provider.list(),
(items) =>
items.some(
(route) =>
route.clientVpnEndpointId === clientVpnEndpointId &&
route.destinationCidrBlock === "192.168.10.0/24" &&
route.targetVpcSubnetId === firstSubnet.subnetId,
),
"route provider list",
);
expect(listed).toContainEqual(
expect.objectContaining({
clientVpnEndpointId,
destinationCidrBlock: "192.168.10.0/24",
targetVpcSubnetId: firstSubnet.subnetId,
}),
);
yield* deployRoute(
"192.168.10.0/24",
firstSubnet.subnetId,
"Initial route",
);
expect(
(yield* readClientVpnRoutes(clientVpnEndpointId)).filter(
(route) =>
route.DestinationCidr === "192.168.10.0/24" &&
route.TargetSubnet === firstSubnet.subnetId &&
route.Status?.Code === "active",
),
).toHaveLength(1);
// AWS has no modify-route API; description changes replace the route at the same identity.
yield* deployRoute(
"192.168.10.0/24",
firstSubnet.subnetId,
"Updated route",
);
yield* waitForClientVpn(
readClientVpnRoutes(clientVpnEndpointId),
(items) =>
items.some(
(route) =>
route.DestinationCidr === "192.168.10.0/24" &&
route.TargetSubnet === firstSubnet.subnetId &&
route.Description === "Updated route" &&
route.Status?.Code === "active",
),
"replaced route description",
);
yield* deployRoute("192.168.11.0/24", firstSubnet.subnetId);
yield* assertClientVpnRouteDeleted(
clientVpnEndpointId,
"192.168.10.0/24",
firstSubnet.subnetId,
);
const replaced = yield* deployRoute(
"192.168.11.0/24",
secondSubnet.subnetId,
);
expect(replaced.targetVpcSubnetId).toBe(secondSubnet.subnetId);
yield* assertClientVpnRouteDeleted(
clientVpnEndpointId,
"192.168.11.0/24",
firstSubnet.subnetId,
);
const replacement = yield* waitForClientVpn(
readClientVpnRoutes(clientVpnEndpointId),
(items) =>
items.some(
(route) =>
route.DestinationCidr === "192.168.11.0/24" &&
route.TargetSubnet === secondSubnet.subnetId &&
route.Status?.Code === "active",
),
"replacement route target",
);
expect(
replacement.find(
(route) =>
route.DestinationCidr === "192.168.11.0/24" &&
route.TargetSubnet === secondSubnet.subnetId,
)?.Description ?? "",
).toBe("");
yield* ec2.deleteClientVpnRoute({
ClientVpnEndpointId: clientVpnEndpointId,
DestinationCidrBlock: "192.168.11.0/24",
TargetVpcSubnetId: secondSubnet.subnetId,
});
yield* assertClientVpnRouteDeleted(
clientVpnEndpointId,
"192.168.11.0/24",
secondSubnet.subnetId,
);
yield* deployRoute("192.168.11.0/24", secondSubnet.subnetId);
yield* waitForClientVpn(
readClientVpnRoutes(clientVpnEndpointId),
(items) =>
items.some(
(route) =>
route.DestinationCidr === "192.168.11.0/24" &&
route.TargetSubnet === secondSubnet.subnetId &&
route.Status?.Code === "active",
),
"recreated out-of-band deleted route",
);
yield* stack.destroy();
yield* assertClientVpnRouteDeleted(
clientVpnEndpointId,
"192.168.11.0/24",
secondSubnet.subnetId,
);
// Deleting a managed route must retain AWS's association-owned VPC routes.
const remaining = yield* readClientVpnRoutes(clientVpnEndpointId);
expect(remaining).toContainEqual(
expect.objectContaining({
DestinationCidr: "10.171.0.0/16",
TargetSubnet: firstSubnet.subnetId,
Status: expect.objectContaining({ Code: "active" }),
}),
);
expect(remaining).toContainEqual(
expect.objectContaining({
DestinationCidr: "10.171.0.0/16",
TargetSubnet: secondSubnet.subnetId,
Status: expect.objectContaining({ Code: "active" }),
}),
);
}),
{ timeout: clientVpnTestTimeout },
);
},
);