1import * as AWS from "@/AWS";
2import * as Core from "@/Test/Core";
3import * as Test from "@/Test/Alchemy";
4import { Region } from "@distilled.cloud/aws/Region";
5import * as sts from "@distilled.cloud/aws/sts";
6import { describe, expect } from "alchemy-test";
7import * as Effect from "effect/Effect";
8import * as Schedule from "effect/Schedule";
9import * as HttpClient from "effect/http/HttpClient";
10import RdsBindingsTestFunctionLive, {
11 RdsBindingsTestFunction,
12} from "./fixtures/handler";
13
14const testOptions = { providers: AWS.providers() };
15const { test, beforeAll, afterAll } = Test.make(testOptions);
16const sharedStack = Core.scratchStack(testOptions, "RdsBindings");
17
18// Lambda function URL cold-start (DNS, IAM propagation, init) can take well
19// over 60s on a fresh deploy.
20const readinessPolicy = Schedule.max([
21 Schedule.fixed("2 seconds"),
22 Schedule.recurs(75),
23]);
24
25let baseUrl: string;
26
27const getJson = (path: string) =>
28 HttpClient.get(`${baseUrl}${path}`).pipe(
29 Effect.flatMap((response) =>
30 response.status >= 500
31 ? Effect.fail(new Error(`transient upstream ${response.status}`))
32 : Effect.succeed(response),
33 ),
34 Effect.retry({
35 schedule: Schedule.max([
36 Schedule.exponential("500 millis"),
37 Schedule.recurs(6),
38 ]),
39 }),
40 Effect.flatMap((r) => r.json),
41 );
42
43describe.sequential(
44 "RDS Bindings",
45 { tags: ["provider:aws", "provider:aws:lambda", "provider:aws:rds", "live"] },
46 () => {
47 beforeAll(
48 Effect.gen(function* () {
49 yield* Effect.logInfo("RDS test setup: destroying previous resources");
50 yield* sharedStack.destroy();
51
52 yield* Effect.logInfo("RDS test setup: deploying fixture");
53 const { functionUrl } = yield* sharedStack.deploy(
54 Effect.gen(function* () {
55 return yield* RdsBindingsTestFunction;
56 }).pipe(Effect.provide(RdsBindingsTestFunctionLive)),
57 );
58
59 expect(functionUrl).toBeTruthy();
60 baseUrl = functionUrl!.replace(/\/+$/, "");
61
62 const readinessUrl = `${baseUrl}/bindings`;
63 yield* HttpClient.get(readinessUrl).pipe(
64 Effect.flatMap((response) =>
65 response.status === 200
66 ? Effect.succeed(response)
67 : Effect.fail(
68 new Error(`Function not ready: ${response.status}`),
69 ),
70 ),
71 Effect.retry({ schedule: readinessPolicy }),
72 );
73 }),
74 { timeout: 240_000 },
75 );
76
77 afterAll(sharedStack.destroy(), { timeout: 120_000 });
78
79 describe("binding registration", () => {
80 test.provider("all capabilities initialize in the runtime", (_stack) =>
81 Effect.gen(function* () {
82 const response = yield* getJson("/bindings");
83 expect((response as any).bound).toHaveLength(12);
84 }),
85 );
86 });
87
88 describe("DescribeDBClusters", () => {
89 test.provider(
90 "surfaces the typed not-found tag for a nonexistent cluster",
91 (_stack) =>
92 Effect.gen(function* () {
93 const response = yield* getJson("/clusters");
94 expect((response as any).tag).toBe("DBClusterNotFoundFault");
95 }),
96 );
97 });
98
99 describe("DescribeDBInstances", () => {
100 test.provider(
101 "surfaces the typed not-found tag for a nonexistent instance",
102 (_stack) =>
103 Effect.gen(function* () {
104 const response = yield* getJson("/instances");
105 expect((response as any).tag).toBe("DBInstanceNotFoundFault");
106 }),
107 );
108 });
109
110 describe("DescribeEvents", () => {
111 test.provider("lists the account's recent RDS events", (_stack) =>
112 Effect.gen(function* () {
113 const response = yield* getJson("/events");
114 expect((response as any).count).toBeGreaterThanOrEqual(0);
115 }),
116 );
117 });
118
119 describe("DescribeDBClusterEndpoints", () => {
120 test.provider("lists the account's cluster endpoints", (_stack) =>
121 Effect.gen(function* () {
122 const response = yield* getJson("/endpoints");
123 expect((response as any).count).toBeGreaterThanOrEqual(0);
124 }),
125 );
126 });
127
128 describe("DescribeDBClusterSnapshots", () => {
129 test.provider("lists the account's cluster snapshots", (_stack) =>
130 Effect.gen(function* () {
131 const response = yield* getJson("/cluster-snapshots");
132 expect((response as any).count).toBeGreaterThanOrEqual(0);
133 }),
134 );
135 });
136
137 describe("DescribeDBSnapshots", () => {
138 test.provider("lists the account's instance snapshots", (_stack) =>
139 Effect.gen(function* () {
140 const response = yield* getJson("/instance-snapshots");
141 expect((response as any).count).toBeGreaterThanOrEqual(0);
142 }),
143 );
144 });
145
146 describe("DeleteDBClusterSnapshot", () => {
147 test.provider(
148 "surfaces the typed not-found tag for a nonexistent snapshot",
149 (_stack) =>
150 Effect.gen(function* () {
151 const response = yield* getJson("/delete-cluster-snapshot-probe");
152 expect((response as any).tag).toBe(
153 "DBClusterSnapshotNotFoundFault",
154 );
155 }),
156 );
157 });
158
159 describe("CopyDBClusterSnapshot", () => {
160 test.provider(
161 "surfaces the typed not-found tag for a nonexistent source snapshot",
162 (_stack) =>
163 Effect.gen(function* () {
164 const response = yield* getJson("/copy-cluster-snapshot-probe");
165 expect((response as any).tag).toBe(
166 "DBClusterSnapshotNotFoundFault",
167 );
168 }),
169 );
170 });
171
172 describe("DeleteDBSnapshot", () => {
173 test.provider(
174 "surfaces the typed not-found tag for a nonexistent snapshot",
175 (_stack) =>
176 Effect.gen(function* () {
177 const response = yield* getJson("/delete-instance-snapshot-probe");
178 expect((response as any).tag).toBe("DBSnapshotNotFoundFault");
179 }),
180 );
181 });
182
183 describe("CopyDBSnapshot", () => {
184 test.provider(
185 "surfaces the typed not-found tag for a nonexistent source snapshot",
186 (_stack) =>
187 Effect.gen(function* () {
188 const response = yield* getJson("/copy-instance-snapshot-probe");
189 expect((response as any).tag).toBe("DBSnapshotNotFoundFault");
190 }),
191 );
192 });
193
194 describe("DescribePendingMaintenanceActions", () => {
195 test.provider(
196 "lists the account's pending maintenance actions",
197 (_stack) =>
198 Effect.gen(function* () {
199 const response = yield* getJson("/pending-maintenance");
200 expect((response as any).count).toBeGreaterThanOrEqual(0);
201 }),
202 );
203 });
204
205 describe("ApplyPendingMaintenanceAction", () => {
206 test.provider(
207 "surfaces the typed not-found tag for a nonexistent resource ARN",
208 (_stack) =>
209 Effect.gen(function* () {
210 // Build a well-formed cluster ARN in this account/region that
211 // cannot exist — the apply call must decode ResourceNotFoundFault.
212 // (`Region`'s service value is itself an Effect — resolve twice.)
213 const region = yield* yield* Region;
214 const identity = yield* sts.getCallerIdentity({});
215 const arn = `arn:aws:rds:${region}:${identity.Account}:cluster:alchemy-nonexistent-rds-cluster-probe`;
216 const response = yield* getJson(
217 `/apply-probe?arn=${encodeURIComponent(arn)}`,
218 );
219 expect((response as any).tag).toBe("ResourceNotFoundFault");
220 }),
221 );
222 });
223 },
224);