1import * as DocDBElastic from "@/AWS/DocDBElastic";
2import * as Lambda from "@/AWS/Lambda";
3import * as Effect from "effect/Effect";
4import * as Layer from "effect/Layer";
5import * as Redacted from "effect/Redacted";
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, "slow-handler.ts");
11
12/**
13 * Env vars the gated test sets (in the test process, before deploy) to hand
14 * the fixture module the default-VPC network it cannot look up itself. They
15 * are read at deploy time only — inside the Lambda runtime they are absent
16 * and the props fall back to `undefined`, which is fine because resource
17 * props are never re-evaluated against the cloud at runtime.
18 */
19export const SUBNETS_ENV = "ALCHEMY_DOCDB_ELASTIC_TEST_SUBNETS";
20export const SECURITY_GROUPS_ENV = "ALCHEMY_DOCDB_ELASTIC_TEST_SGS";
21
22const idsFromEnv = (name: string): string[] | undefined => {
23 const raw = process.env[name];
24 if (!raw) return undefined;
25 const ids = JSON.parse(raw) as string[];
26 return ids.length > 0 ? ids : undefined;
27};
28
29export class DocDBElasticSlowTestFunction extends Lambda.Function<Lambda.Function>()(
30 "DocDBElasticSlowTestFunction",
31) {}
32
33/**
34 * Cluster-scoped binding fixture: deploys a real 1-shard elastic cluster
35 * (~10 minutes, billed per shard-vCPU-hour — gated behind AWS_TEST_SLOW) and
36 * a Lambda bound to it with CreateClusterSnapshot + StopCluster +
37 * StartCluster. The Lambda is not VPC-attached: all three are control-plane
38 * calls.
39 */
40export default DocDBElasticSlowTestFunction.make(
41 {
42 main,
43 functionUrl: true,
44 },
45 Effect.gen(function* () {
46 const network = yield* Effect.sync(() => ({
47 subnetIds: idsFromEnv(SUBNETS_ENV),
48 securityGroupIds: idsFromEnv(SECURITY_GROUPS_ENV),
49 }));
50 const cluster = yield* DocDBElastic.Cluster("SlowDocuments", {
51 adminUserName: "alchemyadmin",
52 adminUserPassword: Redacted.make("AlchemyTestPassw0rd"),
53 shardCapacity: 2,
54 shardCount: 1,
55 subnetIds: network.subnetIds,
56 vpcSecurityGroupIds: network.securityGroupIds,
57 tags: { fixture: "docdb-elastic-bindings" },
58 });
59
60 const createSnapshot = yield* DocDBElastic.CreateClusterSnapshot(cluster);
61 const stopCluster = yield* DocDBElastic.StopCluster(cluster);
62 const startCluster = yield* DocDBElastic.StartCluster(cluster);
63
64 return {
65 fetch: Effect.gen(function* () {
66 const request = yield* HttpServerRequest;
67 const url = new URL(request.originalUrl);
68 const pathname = url.pathname;
69
70 if (request.method === "GET" && pathname === "/snapshot") {
71 const name = url.searchParams.get("name");
72 if (!name) {
73 return yield* HttpServerResponse.json(
74 { error: "name query parameter is required" },
75 { status: 400 },
76 );
77 }
78 const result = yield* createSnapshot({ snapshotName: name });
79 return yield* HttpServerResponse.json({
80 snapshotArn: result.snapshot.snapshotArn,
81 status: result.snapshot.status,
82 });
83 }
84
85 if (request.method === "GET" && pathname === "/stop") {
86 const result = yield* stopCluster();
87 return yield* HttpServerResponse.json({
88 status: result.cluster.status,
89 });
90 }
91
92 if (request.method === "GET" && pathname === "/start") {
93 // Called while the cluster is still STOPPING: a typed rejection
94 // (not AccessDeniedException) proves the grant and the clusterArn
95 // injection without waiting ~10 minutes for STOPPED.
96 const status = yield* startCluster().pipe(
97 Effect.map((result) => result.cluster.status),
98 Effect.catchTag(
99 ["ValidationException", "ResourceNotFoundException"],
100 (e) => Effect.succeed(e._tag),
101 ),
102 );
103 return yield* HttpServerResponse.json({ status });
104 }
105
106 return yield* HttpServerResponse.json(
107 { error: "Not found", method: request.method, pathname },
108 { status: 404 },
109 );
110 }).pipe(Effect.orDie),
111 };
112 }).pipe(
113 Effect.provide(
114 Layer.mergeAll(
115 DocDBElastic.CreateClusterSnapshotHttp,
116 DocDBElastic.StopClusterHttp,
117 DocDBElastic.StartClusterHttp,
118 ),
119 ),
120 ),
121);