1import * as Lambda from "@/AWS/Lambda";
2import * as Redshift from "@/AWS/Redshift";
3import * as Effect from "effect/Effect";
4import * as Layer from "effect/Layer";
5import { HttpServerRequest } from "effect/http/HttpServerRequest";
6import * as HttpServerResponse from "effect/http/HttpServerResponse";
7import path from "pathe";
8
9const main = path.resolve(import.meta.dirname, "bindings-handler.ts");
10
11// Well-formed-but-nonexistent identifiers — the probe routes drive the
12// bindings against them so the fixture exercises the IAM grants and the
13// typed error decode at zero cost (no cluster/snapshot is ever created).
14const NONEXISTENT_CLUSTER_ID = "alchemy-nonexistent-redshift-probe";
15const NONEXISTENT_SNAPSHOT_ID = "alchemy-nonexistent-redshift-snapshot-probe";
16
17export class RedshiftBindingsTestFunction extends Lambda.Function<Lambda.Function>()(
18 "RedshiftBindingsTestFunction",
19) {}
20
21/**
22 * Account-level binding fixture: no Redshift cluster is ever created. List
23 * routes prove each grant and response decode against real (possibly empty)
24 * account data; probe routes drive identifier-addressed operations against
25 * nonexistent identifiers and must surface the service's typed not-found
26 * tag — an IAM gap would surface AccessDeniedException and fail the route
27 * with an opaque 500.
28 *
29 * The cluster-scoped bindings (`PauseCluster`, `ResumeCluster`,
30 * `RebootCluster`, `CreateClusterSnapshot`) resolve a bound {@link
31 * Redshift.Cluster} at deploy time, so they need a live cluster (~5-10 min,
32 * billed hourly) and are exercised only under AWS_TEST_REDSHIFT-gated
33 * lifecycle runs; they share the same `makeRedshiftClusterHttpBinding`
34 * scaffold the account-level routes prove here.
35 */
36export default RedshiftBindingsTestFunction.make(
37 {
38 main,
39 functionUrl: true,
40 },
41 Effect.gen(function* () {
42 const describeClusters = yield* Redshift.DescribeClusters();
43 const describeClusterSnapshots = yield* Redshift.DescribeClusterSnapshots();
44 const describeEvents = yield* Redshift.DescribeEvents();
45 const deleteClusterSnapshot = yield* Redshift.DeleteClusterSnapshot();
46 const copyClusterSnapshot = yield* Redshift.CopyClusterSnapshot();
47
48 const bound = {
49 describeClusters,
50 describeClusterSnapshots,
51 describeEvents,
52 deleteClusterSnapshot,
53 copyClusterSnapshot,
54 };
55
56 return {
57 fetch: Effect.gen(function* () {
58 const request = yield* HttpServerRequest;
59 const url = new URL(request.originalUrl);
60 const pathname = url.pathname;
61
62 if (request.method === "GET" && pathname === "/bindings") {
63 return yield* HttpServerResponse.json({
64 bound: Object.keys(bound),
65 });
66 }
67
68 if (request.method === "GET" && pathname === "/clusters") {
69 // A nonexistent cluster identifier must surface the service's
70 // typed not-found tag.
71 const tag = yield* describeClusters({
72 ClusterIdentifier: NONEXISTENT_CLUSTER_ID,
73 }).pipe(
74 Effect.map(() => "Found"),
75 Effect.catchTag("ClusterNotFoundFault", (e) =>
76 Effect.succeed(e._tag),
77 ),
78 );
79 return yield* HttpServerResponse.json({ tag });
80 }
81
82 if (request.method === "GET" && pathname === "/snapshots") {
83 // Listing the account's manual snapshots succeeds (possibly
84 // empty) — proves the grant and the schema decode.
85 const result = yield* describeClusterSnapshots({
86 SnapshotType: "manual",
87 });
88 return yield* HttpServerResponse.json({
89 count: (result.Snapshots ?? []).length,
90 });
91 }
92
93 if (request.method === "GET" && pathname === "/events") {
94 // Listing the account's recent Redshift events succeeds (possibly
95 // empty) — proves the grant and the schema decode.
96 const result = yield* describeEvents({ Duration: 60 });
97 return yield* HttpServerResponse.json({
98 count: (result.Events ?? []).length,
99 });
100 }
101
102 if (request.method === "GET" && pathname === "/delete-snapshot-probe") {
103 // Deleting a nonexistent snapshot must surface the typed
104 // not-found tag — proves the write-side grant without ever
105 // creating a snapshot.
106 const tag = yield* deleteClusterSnapshot({
107 SnapshotIdentifier: NONEXISTENT_SNAPSHOT_ID,
108 }).pipe(
109 Effect.map(() => "Deleted"),
110 Effect.catchTag("ClusterSnapshotNotFoundFault", (e) =>
111 Effect.succeed(e._tag),
112 ),
113 );
114 return yield* HttpServerResponse.json({ tag });
115 }
116
117 if (request.method === "GET" && pathname === "/copy-snapshot-probe") {
118 // Copying from a nonexistent source snapshot must surface the
119 // typed not-found tag — proves the copy grant at zero cost.
120 const tag = yield* copyClusterSnapshot({
121 SourceSnapshotIdentifier: NONEXISTENT_SNAPSHOT_ID,
122 TargetSnapshotIdentifier: `${NONEXISTENT_SNAPSHOT_ID}-copy`,
123 }).pipe(
124 Effect.map(() => "Copied"),
125 Effect.catchTag("ClusterSnapshotNotFoundFault", (e) =>
126 Effect.succeed(e._tag),
127 ),
128 );
129 return yield* HttpServerResponse.json({ tag });
130 }
131
132 return yield* HttpServerResponse.json(
133 { error: "Not found", method: request.method, pathname },
134 { status: 404 },
135 );
136 }).pipe(Effect.orDie),
137 };
138 }).pipe(
139 Effect.provide(
140 Layer.mergeAll(
141 Redshift.DescribeClustersHttp,
142 Redshift.DescribeClusterSnapshotsHttp,
143 Redshift.DescribeEventsHttp,
144 Redshift.DeleteClusterSnapshotHttp,
145 Redshift.CopyClusterSnapshotHttp,
146 ),
147 ),
148 ),
149);