1import * as AWS from "@/AWS";
2import { DBSubnetGroup } from "@/AWS/Neptune";
3import * as Test from "@/Test/Alchemy";
4import * as EC2 from "@distilled.cloud/aws/ec2";
5import * as neptune from "@distilled.cloud/aws/neptune";
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// Resolve two default-for-AZ subnets from the account's default VPC.
14const defaultSubnetIds = Effect.gen(function* () {
15 const vpc = yield* getDefaultVpc;
16 const subnets = yield* EC2.describeSubnets({
17 Filters: [
18 { Name: "vpc-id", Values: [vpc.vpcId] },
19 { Name: "default-for-az", Values: ["true"] },
20 ],
21 });
22 const ids = (subnets.Subnets ?? [])
23 .map((s) => s.SubnetId)
24 .filter((id): id is `subnet-${string}` => id !== undefined)
25 .sort();
26 if (ids.length < 2) {
27 return yield* Effect.die(
28 new Error("default VPC has fewer than 2 default-for-AZ subnets"),
29 );
30 }
31 return ids;
32});
33
34const assertGone = (name: string) =>
35 neptune.describeDBSubnetGroups({ DBSubnetGroupName: name }).pipe(
36 Effect.flatMap(() =>
37 Effect.fail(new Error(`subnet group '${name}' still exists`)),
38 ),
39 Effect.catchTag("DBSubnetGroupNotFoundFault", () => Effect.void),
40 Effect.retry({
41 schedule: Schedule.max([
42 Schedule.fixed("2 seconds"),
43 Schedule.recurs(10),
44 ]),
45 }),
46 );
47
48test.provider(
49 "create, update description, delete a Neptune DB subnet group",
50 (stack) =>
51 Effect.gen(function* () {
52 yield* stack.destroy();
53
54 const subnetIds = yield* defaultSubnetIds;
55
56 const { group } = yield* stack.deploy(
57 Effect.gen(function* () {
58 const group = yield* DBSubnetGroup("Subnets", {
59 description: "alchemy neptune subnet group",
60 subnetIds: subnetIds.slice(0, 2),
61 tags: { fixture: "neptune-subnet-group" },
62 });
63 return { group };
64 }),
65 );
66
67 expect(group.dbSubnetGroupName).toBeDefined();
68 expect(group.dbSubnetGroupArn).toContain(":subgrp:");
69 expect(new Set(group.subnetIds)).toEqual(new Set(subnetIds.slice(0, 2)));
70
71 // Out-of-band verification.
72 const described = yield* neptune.describeDBSubnetGroups({
73 DBSubnetGroupName: group.dbSubnetGroupName,
74 });
75 const observed = described.DBSubnetGroups?.[0];
76 expect(observed?.DBSubnetGroupDescription).toBe(
77 "alchemy neptune subnet group",
78 );
79 expect(observed?.VpcId).toBeDefined();
80
81 // Update the description in place (name unchanged).
82 const { group: updated } = yield* stack.deploy(
83 Effect.gen(function* () {
84 const group = yield* DBSubnetGroup("Subnets", {
85 description: "alchemy neptune subnet group v2",
86 subnetIds: subnetIds.slice(0, 2),
87 tags: { fixture: "neptune-subnet-group", env: "test" },
88 });
89 return { group };
90 }),
91 );
92 expect(updated.dbSubnetGroupName).toBe(group.dbSubnetGroupName);
93
94 const redescribed = yield* neptune.describeDBSubnetGroups({
95 DBSubnetGroupName: group.dbSubnetGroupName,
96 });
97 expect(redescribed.DBSubnetGroups?.[0]?.DBSubnetGroupDescription).toBe(
98 "alchemy neptune subnet group v2",
99 );
100
101 yield* stack.destroy();
102 yield* assertGone(group.dbSubnetGroupName);
103 }),
104 {
105 tags: ["provider:aws", "provider:aws:ec2", "provider:aws:neptune", "live"],
106 timeout: 240_000,
107 },
108);