1import * as Lambda from "@/AWS/Lambda";
2import * as Neptune from "@/AWS/Neptune";
3import * as Effect from "effect/Effect";
4import * as Layer from "effect/Layer";
5import * as Stream from "effect/Stream";
6import { HttpServerRequest } from "effect/http/HttpServerRequest";
7import * as HttpServerResponse from "effect/http/HttpServerResponse";
8import path from "pathe";
9
10const main = path.resolve(import.meta.dirname, "handler.ts");
11
12// Well-formed-but-nonexistent identifiers — the probe routes drive the
13// bindings against them so the fixture exercises the IAM grants and the
14// typed error decode at zero cost (no cluster/snapshot is ever created).
15const NONEXISTENT_CLUSTER_ID = "alchemy-nonexistent-neptune-probe";
16const NONEXISTENT_INSTANCE_ID = "alchemy-nonexistent-neptune-instance-probe";
17const NONEXISTENT_SNAPSHOT_ID = "alchemy-nonexistent-neptune-snapshot-probe";
18
19export class NeptuneBindingsTestFunction extends Lambda.Function<Lambda.Function>()(
20 "NeptuneBindingsTestFunction",
21) {}
22
23/**
24 * Account-level binding fixture: no Neptune cluster is ever created. List
25 * routes prove each grant and response decode against real (possibly empty)
26 * account data; probe routes drive identifier-addressed operations against
27 * nonexistent identifiers and must surface the service's typed not-found
28 * tag — an IAM gap would surface AccessDeniedException and fail the route
29 * with an opaque 500.
30 *
31 * The cluster/instance-scoped bindings (`CreateDBClusterSnapshot`,
32 * `FailoverDBCluster`, `StartDBCluster`, `StopDBCluster`,
33 * `RebootDBInstance`) resolve a bound resource at deploy time, so they need
34 * a live cluster (~10 min, billed hourly) and are exercised only under
35 * AWS_TEST_SLOW-gated lifecycle runs.
36 */
37export default NeptuneBindingsTestFunction.make(
38 {
39 main,
40 functionUrl: true,
41 },
42 Effect.gen(function* () {
43 // Event source: subscribe the host to Neptune cluster events flowing
44 // through the shared RDS eventing plane. The deploy proves the
45 // EventBridge rule + invoke permission wiring.
46 yield* Neptune.consumeNeptuneEvents(
47 { kinds: ["db-cluster", "db-cluster-snapshot"] },
48 (events) =>
49 Stream.runForEach(events, (event) =>
50 Effect.log(
51 `neptune event: ${event["detail-type"]} -> ${event.resources.join(", ")}`,
52 ),
53 ),
54 );
55
56 const describeDBClusters = yield* Neptune.DescribeDBClusters();
57 const describeDBInstances = yield* Neptune.DescribeDBInstances();
58 const describeDBClusterEndpoints =
59 yield* Neptune.DescribeDBClusterEndpoints();
60 const describeEvents = yield* Neptune.DescribeEvents();
61 const describeDBClusterSnapshots =
62 yield* Neptune.DescribeDBClusterSnapshots();
63 const deleteDBClusterSnapshot = yield* Neptune.DeleteDBClusterSnapshot();
64 const copyDBClusterSnapshot = yield* Neptune.CopyDBClusterSnapshot();
65 const describePendingMaintenanceActions =
66 yield* Neptune.DescribePendingMaintenanceActions();
67 const applyPendingMaintenanceAction =
68 yield* Neptune.ApplyPendingMaintenanceAction();
69
70 const bound = {
71 describeDBClusters,
72 describeDBInstances,
73 describeDBClusterEndpoints,
74 describeEvents,
75 describeDBClusterSnapshots,
76 deleteDBClusterSnapshot,
77 copyDBClusterSnapshot,
78 describePendingMaintenanceActions,
79 applyPendingMaintenanceAction,
80 };
81
82 return {
83 fetch: Effect.gen(function* () {
84 const request = yield* HttpServerRequest;
85 const url = new URL(request.originalUrl);
86 const pathname = url.pathname;
87
88 if (request.method === "GET" && pathname === "/bindings") {
89 return yield* HttpServerResponse.json({
90 bound: Object.keys(bound),
91 });
92 }
93
94 if (request.method === "GET" && pathname === "/clusters") {
95 // A nonexistent cluster identifier must surface the service's
96 // typed not-found tag — an IAM gap would surface
97 // AccessDeniedException and fail the route with an opaque 500.
98 const tag = yield* describeDBClusters({
99 DBClusterIdentifier: NONEXISTENT_CLUSTER_ID,
100 }).pipe(
101 Effect.map(() => "Found"),
102 Effect.catchTag("DBClusterNotFoundFault", (e) =>
103 Effect.succeed(e._tag),
104 ),
105 );
106 return yield* HttpServerResponse.json({ tag });
107 }
108
109 if (request.method === "GET" && pathname === "/instances") {
110 // Nonexistent instance identifier → typed not-found tag.
111 const tag = yield* describeDBInstances({
112 DBInstanceIdentifier: NONEXISTENT_INSTANCE_ID,
113 }).pipe(
114 Effect.map(() => "Found"),
115 Effect.catchTag("DBInstanceNotFoundFault", (e) =>
116 Effect.succeed(e._tag),
117 ),
118 );
119 return yield* HttpServerResponse.json({ tag });
120 }
121
122 if (request.method === "GET" && pathname === "/endpoints") {
123 // Listing the account's cluster endpoints succeeds (possibly
124 // empty) — proves the grant and the schema decode.
125 const result = yield* describeDBClusterEndpoints();
126 return yield* HttpServerResponse.json({
127 count: (result.DBClusterEndpoints ?? []).length,
128 });
129 }
130
131 if (request.method === "GET" && pathname === "/events") {
132 // Listing the account's recent Neptune-plane events succeeds
133 // (possibly empty) — proves the grant and the schema decode.
134 const result = yield* describeEvents();
135 return yield* HttpServerResponse.json({
136 count: (result.Events ?? []).length,
137 });
138 }
139
140 if (request.method === "GET" && pathname === "/snapshots") {
141 // Listing the account's cluster snapshots succeeds (possibly
142 // empty) — proves the grant and the schema decode.
143 const result = yield* describeDBClusterSnapshots();
144 return yield* HttpServerResponse.json({
145 count: (result.DBClusterSnapshots ?? []).length,
146 });
147 }
148
149 if (request.method === "GET" && pathname === "/delete-snapshot-probe") {
150 // Deleting a nonexistent snapshot must surface the typed
151 // not-found tag — proves the write-side grant without ever
152 // creating a snapshot.
153 const tag = yield* deleteDBClusterSnapshot({
154 DBClusterSnapshotIdentifier: NONEXISTENT_SNAPSHOT_ID,
155 }).pipe(
156 Effect.map(() => "Deleted"),
157 Effect.catchTag("DBClusterSnapshotNotFoundFault", (e) =>
158 Effect.succeed(e._tag),
159 ),
160 );
161 return yield* HttpServerResponse.json({ tag });
162 }
163
164 if (request.method === "GET" && pathname === "/copy-snapshot-probe") {
165 // Copying from a nonexistent source snapshot must surface the
166 // typed not-found tag — proves the copy grant at zero cost.
167 const tag = yield* copyDBClusterSnapshot({
168 SourceDBClusterSnapshotIdentifier: NONEXISTENT_SNAPSHOT_ID,
169 TargetDBClusterSnapshotIdentifier: `${NONEXISTENT_SNAPSHOT_ID}-copy`,
170 }).pipe(
171 Effect.map(() => "Copied"),
172 Effect.catchTag("DBClusterSnapshotNotFoundFault", (e) =>
173 Effect.succeed(e._tag),
174 ),
175 );
176 return yield* HttpServerResponse.json({ tag });
177 }
178
179 if (request.method === "GET" && pathname === "/pending-maintenance") {
180 // Listing pending maintenance actions succeeds (possibly empty).
181 const result = yield* describePendingMaintenanceActions();
182 return yield* HttpServerResponse.json({
183 count: (result.PendingMaintenanceActions ?? []).length,
184 });
185 }
186
187 if (request.method === "GET" && pathname === "/apply-probe") {
188 // Applying maintenance to a nonexistent resource ARN must surface
189 // the typed ResourceNotFoundFault — proves the apply grant.
190 const arn = url.searchParams.get("arn") ?? "";
191 const tag = yield* applyPendingMaintenanceAction({
192 ResourceIdentifier: arn,
193 ApplyAction: "system-update",
194 OptInType: "next-maintenance",
195 }).pipe(
196 Effect.map(() => "Applied"),
197 Effect.catchTag("ResourceNotFoundFault", (e) =>
198 Effect.succeed(e._tag),
199 ),
200 );
201 return yield* HttpServerResponse.json({ tag });
202 }
203
204 return yield* HttpServerResponse.json(
205 { error: "Not found", method: request.method, pathname },
206 { status: 404 },
207 );
208 }).pipe(Effect.orDie),
209 };
210 }).pipe(
211 Effect.provide(
212 Layer.mergeAll(
213 Lambda.EventSource,
214 Neptune.DescribeDBClustersHttp,
215 Neptune.DescribeDBInstancesHttp,
216 Neptune.DescribeDBClusterEndpointsHttp,
217 Neptune.DescribeEventsHttp,
218 Neptune.DescribeDBClusterSnapshotsHttp,
219 Neptune.DeleteDBClusterSnapshotHttp,
220 Neptune.CopyDBClusterSnapshotHttp,
221 Neptune.DescribePendingMaintenanceActionsHttp,
222 Neptune.ApplyPendingMaintenanceActionHttp,
223 ),
224 ),
225 ),
226);