1import * as AWS from "@/AWS";
2import { SubnetGroup } from "@/AWS/DAX";
3import * as Test from "@/Test/Alchemy";
4import * as dax from "@distilled.cloud/aws/dax";
5import * as EC2 from "@distilled.cloud/aws/ec2";
6import { expect } from "alchemy-test";
7import * as Effect from "effect/Effect";
8import * as Schedule from "effect/Schedule";
9import { getDefaultVpc } from "../DefaultVpc.ts";
10
11const { test } = Test.make({ providers: AWS.providers() });
12
13// Ungated typed-error probe: prove the distilled error union carries the
14// not-found tag this provider's read/delete paths depend on.
15test.provider(
16 "describeSubnetGroups on a nonexistent group fails with SubnetGroupNotFoundFault",
17 () =>
18 Effect.gen(function* () {
19 const error = yield* Effect.flip(
20 dax.describeSubnetGroups({
21 SubnetGroupNames: ["alchemy-nonexistent-dax-subnet-probe"],
22 }),
23 );
24 expect(error._tag).toBe("SubnetGroupNotFoundFault");
25 }),
26 { tags: ["provider:aws", "provider:aws:dax", "live"] },
27);
28
29// Resolve default-for-AZ subnets from the account's default VPC.
30const defaultSubnetIds = Effect.gen(function* () {
31 const vpc = yield* getDefaultVpc;
32 const subnets = yield* EC2.describeSubnets({
33 Filters: [
34 { Name: "vpc-id", Values: [vpc.vpcId] },
35 { Name: "default-for-az", Values: ["true"] },
36 ],
37 });
38 const ids = (subnets.Subnets ?? [])
39 .map((s) => s.SubnetId)
40 .filter((id): id is `subnet-${string}` => id !== undefined)
41 .sort();
42 if (ids.length < 3) {
43 return yield* Effect.die(
44 new Error("default VPC has fewer than 3 default-for-AZ subnets"),
45 );
46 }
47 return ids;
48});
49
50const assertGone = (name: string) =>
51 dax.describeSubnetGroups({ SubnetGroupNames: [name] }).pipe(
52 Effect.flatMap(() =>
53 Effect.fail(new Error(`subnet group '${name}' still exists`)),
54 ),
55 Effect.catchTag("SubnetGroupNotFoundFault", () => Effect.void),
56 Effect.retry({
57 schedule: Schedule.max([
58 Schedule.fixed("2 seconds"),
59 Schedule.recurs(10),
60 ]),
61 }),
62 );
63
64test.provider(
65 "create, update description and subnets, delete a DAX subnet group",
66 (stack) =>
67 Effect.gen(function* () {
68 yield* stack.destroy();
69
70 const subnetIds = yield* defaultSubnetIds;
71
72 const { group } = yield* stack.deploy(
73 Effect.gen(function* () {
74 const group = yield* SubnetGroup("Subnets", {
75 description: "alchemy dax subnet group",
76 subnetIds: subnetIds.slice(0, 2),
77 });
78 return { group };
79 }),
80 );
81
82 expect(group.subnetGroupName).toBeDefined();
83 expect(group.vpcId).toBeDefined();
84 expect(new Set(group.subnetIds)).toEqual(new Set(subnetIds.slice(0, 2)));
85
86 // Out-of-band verification via distilled.
87 const described = yield* dax.describeSubnetGroups({
88 SubnetGroupNames: [group.subnetGroupName],
89 });
90 const observed = described.SubnetGroups?.[0];
91 expect(observed?.Description).toBe("alchemy dax subnet group");
92 expect(observed?.VpcId).toBeDefined();
93
94 // Update the description and widen the subnet set in place.
95 const { group: updated } = yield* stack.deploy(
96 Effect.gen(function* () {
97 const group = yield* SubnetGroup("Subnets", {
98 description: "alchemy dax subnet group v2",
99 subnetIds: subnetIds.slice(0, 3),
100 });
101 return { group };
102 }),
103 );
104 expect(updated.subnetGroupName).toBe(group.subnetGroupName);
105 expect(new Set(updated.subnetIds)).toEqual(
106 new Set(subnetIds.slice(0, 3)),
107 );
108
109 const redescribed = yield* dax.describeSubnetGroups({
110 SubnetGroupNames: [group.subnetGroupName],
111 });
112 expect(redescribed.SubnetGroups?.[0]?.Description).toBe(
113 "alchemy dax subnet group v2",
114 );
115
116 yield* stack.destroy();
117 yield* assertGone(group.subnetGroupName);
118 }),
119 {
120 tags: ["provider:aws", "provider:aws:dax", "provider:aws:ec2", "live"],
121 timeout: 240_000,
122 },
123);