1import * as AWS from "@/AWS";
2import * as Core from "@/Test/Core";
3import * as Test from "./VpcTest.ts";
4import { describe, expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import * as Schedule from "effect/Schedule";
7import * as HttpClient from "effect/http/HttpClient";
8import NetworkTestFunctionLive, {
9 NetworkTestFunction,
10} from "./fixtures/network-function";
11
12const testOptions = { providers: AWS.providers() };
13const { test, beforeAll, afterAll } = Test.make(testOptions);
14const sharedStack = Core.scratchStack(testOptions, "EC2NetworkFunction");
15
16// Lambda function URL cold-start (DNS, IAM propagation, init) can take
17// well over 60s on a fresh deploy — budget ~150s of readiness polling.
18const readinessPolicy = Schedule.max([
19 Schedule.fixed("2 seconds"),
20 Schedule.recurs(75),
21]);
22
23let baseUrl: string;
24
25describe(
26 "EC2.Network composed in a Lambda layer",
27 { tags: ["provider:aws", "provider:aws:ec2", "provider:aws:lambda", "live"] },
28 () => {
29 beforeAll(
30 Effect.gen(function* () {
31 yield* sharedStack.destroy();
32
33 const { functionUrl } = yield* sharedStack.deploy(
34 Effect.gen(function* () {
35 return yield* NetworkTestFunction;
36 }).pipe(Effect.provide(NetworkTestFunctionLive)),
37 );
38
39 expect(functionUrl).toBeTruthy();
40 baseUrl = functionUrl!.replace(/\/+$/, "");
41
42 // The function role has NO ec2:Describe* permissions and the runtime
43 // does not provide `AWSEnvironment` — a 200 here proves the `Network`
44 // composition re-executes cleanly at Lambda init.
45 yield* HttpClient.get(`${baseUrl}/network`).pipe(
46 Effect.timeout("4 seconds"),
47 Effect.flatMap((response) =>
48 response.status === 200
49 ? Effect.succeed(response)
50 : Effect.fail(
51 new Error(`Function not ready: ${response.status}`),
52 ),
53 ),
54 Effect.retry({ schedule: readinessPolicy }),
55 );
56 }),
57 // This nested setup hook is scheduled while the file may still be queued
58 // on the shared VPC-capacity lease. Give coordination the suite wall;
59 // every cloud/HTTP wait inside remains independently bounded.
60 { timeout: 3_600_000 },
61 );
62
63 afterAll(sharedStack.destroy(), { timeout: 240_000 });
64
65 test.provider(
66 "Lambda INITs cleanly and resolves Network outputs at runtime",
67 (_stack) =>
68 Effect.gen(function* () {
69 const response = yield* HttpClient.get(`${baseUrl}/network`);
70 expect(response.status).toBe(200);
71
72 const body = (yield* response.json) as {
73 vpcId: string;
74 subnetId: string;
75 };
76 expect(body.vpcId).toMatch(/^vpc-/);
77 expect(body.subnetId).toMatch(/^subnet-/);
78 }),
79 { timeout: 60_000 },
80 );
81 },
82);