1import * as AWS from "@/AWS";
2import { DBSubnetGroup } from "@/AWS/RDS/DBSubnetGroup.ts";
3import type { SubnetId } from "@/AWS/EC2/Subnet.ts";
4import * as Provider from "@/Provider";
5import * as Test from "@/Test/Alchemy";
6import * as EC2 from "@distilled.cloud/aws/ec2";
7import { expect } from "alchemy-test";
8import * as Effect from "effect/Effect";
9
10const { test } = Test.make({ providers: AWS.providers() });
11
12// Default read-only path (AWS account/region-scoped collection): resolve the
13// provider via the typed `Provider.findProvider(DBSubnetGroup)` so `list()`'s
14// element type is the exact `DBSubnetGroup["Attributes"]` shape, then call
15// `list()` without deploying. This always runs and exercises the full
16// `describeDBSubnetGroups` pagination + Attributes-mapping code, asserting a
17// well-typed `Attributes[]`.
18//
19// We do NOT deploy here because a DB subnet group needs subnets spanning >= 2
20// AZs, and the shared testing account has NO default VPC, is at its 5-VPC limit,
21// and (verified live) currently exposes only a single available subnet in a
22// single AZ — so there is no reusable VPC to carve a multi-AZ group from. The
23// deploy-backed assertion is gated below behind AWS_TEST_RDS_DBSUBNETGROUP=1 for
24// accounts that have multi-AZ subnets available.
25test.provider(
26 "list returns well-typed DBSubnetGroup attributes",
27 () =>
28 Effect.gen(function* () {
29 const provider = yield* Provider.findProvider(DBSubnetGroup);
30 const all = yield* provider.list();
31
32 expect(Array.isArray(all)).toBe(true);
33 for (const g of all) {
34 expect(typeof g.dbSubnetGroupName).toBe("string");
35 expect(Array.isArray(g.subnetIds)).toBe(true);
36 expect(typeof g.tags).toBe("object");
37 }
38 }),
39 { tags: ["provider:aws", "provider:aws:rds", "live"] },
40);
41
42// Deploy-backed list test. Gated behind AWS_TEST_RDS_DBSUBNETGROUP=1 because a
43// DB subnet group requires subnets in >= 2 distinct AZs, which the shared
44// testing account cannot provide (no default VPC, at the VPC limit, only one
45// available subnet in one AZ). On an account that has multi-AZ subnets, this
46// reuses an existing VPC's subnets (subnets don't count against the VPC limit),
47// deploys a subnet group, resolves the provider via the typed
48// `Provider.findProvider(DBSubnetGroup)`, lists, and asserts presence — all
49// bracketed by `stack.destroy()` at start and end.
50test.provider.skipIf(!process.env.AWS_TEST_RDS_DBSUBNETGROUP)(
51 "list enumerates the deployed DB subnet group",
52 (stack) =>
53 Effect.gen(function* () {
54 yield* stack.destroy();
55
56 const subnetsResult = yield* EC2.describeSubnets({});
57 const available = (subnetsResult.Subnets ?? []).filter(
58 (s) =>
59 s.State === "available" &&
60 !!s.SubnetId &&
61 !!s.VpcId &&
62 !!s.AvailabilityZone,
63 );
64
65 // Group available subnets by VPC, keeping one subnet per AZ, then pick a
66 // VPC with subnets in >= 2 distinct AZs.
67 const byVpc = new Map<string, Map<string, string>>();
68 for (const s of available) {
69 let byAz = byVpc.get(s.VpcId!);
70 if (!byAz) {
71 byAz = new Map();
72 byVpc.set(s.VpcId!, byAz);
73 }
74 if (!byAz.has(s.AvailabilityZone!)) {
75 byAz.set(s.AvailabilityZone!, s.SubnetId!);
76 }
77 }
78
79 let chosen: string[] | undefined;
80 for (const byAz of byVpc.values()) {
81 if (byAz.size >= 2) {
82 chosen = Array.from(byAz.values()).slice(0, 2);
83 break;
84 }
85 }
86
87 if (!chosen) {
88 // Exact reason for a clean skip:
89 // "no existing VPC has available subnets in >= 2 distinct AZs".
90 return yield* Effect.fail(
91 new Error(
92 "no existing VPC has available subnets in >= 2 distinct AZs",
93 ),
94 );
95 }
96
97 const subnetIds = chosen as SubnetId[];
98
99 const group = yield* stack.deploy(
100 Effect.gen(function* () {
101 return yield* DBSubnetGroup("ListDBSubnetGroup", {
102 dbSubnetGroupName: "alchemy-test-dbsng-list",
103 description: "Alchemy list() test subnet group",
104 subnetIds,
105 });
106 }),
107 );
108
109 const provider = yield* Provider.findProvider(DBSubnetGroup);
110 const all = yield* provider.list();
111
112 expect(Array.isArray(all)).toBe(true);
113 expect(
114 all.some((g) => g.dbSubnetGroupName === group.dbSubnetGroupName),
115 ).toBe(true);
116
117 yield* stack.destroy();
118 }),
119 { tags: ["provider:aws", "provider:aws:ec2", "provider:aws:rds", "live"] },
120);