t3-code-android-nightly/.repos/alchemy-effect/packages/alchemy/test/AWS/EFS/MountTarget.test.ts
Julius Marminge 6f9cea00ae
chore(refs): sync Effect and Alchemy references to 4.0.1 and beta.80 (#16170)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 13:22:30 -07:00

199 lines
6.9 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import * as AWS from "@/AWS";
import type { VpcId } from "@/AWS/EC2";
import * as Core from "@/Test/Core";
import * as Test from "@/Test/Alchemy";
import * as EC2 from "@distilled.cloud/aws/ec2";
import * as efs from "@distilled.cloud/aws/efs";
import { describe, expect } from "alchemy-test";
import * as Effect from "effect/Effect";
import * as Schedule from "effect/Schedule";
import { getDefaultVpc } from "../DefaultVpc.ts";
const testOptions = { providers: AWS.providers() };
const { test, beforeAll, afterAll } = Test.make(testOptions);
const sharedStack = Core.scratchStack(testOptions, "EFSMountTarget");
// Mount-target churn is the slow part of EFS (1–3 minutes each way), so the
// suite deploys ONE mount target in a `beforeAll`, drives updates against it
// in tests, and tears it down once in `afterAll`. Multi-AZ coverage is gated
// behind AWS_TEST_EFS_MULTI_AZ=1 because it doubles the churn.
let vpcId: VpcId;
let subnetId: string;
let defaultSecurityGroupId: string;
let extraSecurityGroupId: string;
let fileSystemId: string;
let mountTargetId: string;
// Resolve the default VPC, a default subnet, and the default security group
// once. Runs inside a deploy/test effect (the `beforeAll` hook itself has no
// AWS context).
const resolveNetwork = Effect.gen(function* () {
if (subnetId !== undefined) return;
const vpc = yield* getDefaultVpc;
vpcId = vpc.vpcId;
const subnets = yield* EC2.describeSubnets({
Filters: [
{ Name: "vpc-id", Values: [vpc.vpcId] },
{ Name: "default-for-az", Values: ["true"] },
],
});
subnetId = subnets.Subnets![0]!.SubnetId!;
const groups = yield* EC2.describeSecurityGroups({
Filters: [
{ Name: "vpc-id", Values: [vpc.vpcId] },
{ Name: "group-name", Values: ["default"] },
],
});
defaultSecurityGroupId = groups.SecurityGroups![0]!.GroupId!;
});
const infra = (securityGroups?: () => string[]) =>
Effect.gen(function* () {
yield* resolveNetwork;
const files = yield* AWS.EFS.FileSystem("MtFiles");
const extraSg = yield* AWS.EC2.SecurityGroup("MtSg", {
vpcId,
description: "alchemy EFS mount target test",
});
const target = yield* AWS.EFS.MountTarget("MtTarget", {
fileSystemId: files.fileSystemId,
subnetId,
...(securityGroups !== undefined
? { securityGroups: securityGroups() }
: {}),
});
return { files, target, extraSg };
});
describe.sequential(
"EFS MountTarget",
{ tags: ["provider:aws", "provider:aws:ec2", "provider:aws:efs", "live"] },
() => {
beforeAll(
Effect.gen(function* () {
yield* sharedStack.destroy();
// initial deploy: no explicit security groups → the VPC default group
const deployed = yield* sharedStack.deploy(infra());
fileSystemId = deployed.files.fileSystemId;
mountTargetId = deployed.target.mountTargetId;
extraSecurityGroupId = deployed.extraSg.groupId;
}),
{ timeout: 240_000 },
);
afterAll(sharedStack.destroy(), { timeout: 240_000 });
test.provider(
"mount target is available in the subnet with the default security group",
() =>
Effect.gen(function* () {
expect(mountTargetId).toMatch(/^fsmt-/);
const observed = yield* efs
.describeMountTargets({ MountTargetId: mountTargetId })
.pipe(Effect.map((r) => r.MountTargets![0]));
expect(observed.LifeCycleState).toBe("available");
expect(observed.SubnetId).toBe(subnetId);
expect(observed.FileSystemId).toBe(fileSystemId);
expect(observed.IpAddress).toBeTruthy();
const groups = yield* efs.describeMountTargetSecurityGroups({
MountTargetId: mountTargetId,
});
expect(groups.SecurityGroups).toEqual([defaultSecurityGroupId]);
}),
{ timeout: 60_000 },
);
test.provider(
"security groups are modified in place",
() =>
Effect.gen(function* () {
const updated = yield* sharedStack.deploy(
infra(() => [defaultSecurityGroupId, extraSecurityGroupId]),
);
// same mount target — securityGroups is mutable
expect(updated.target.mountTargetId).toBe(mountTargetId);
const observed = yield* efs
.describeMountTargetSecurityGroups({
MountTargetId: mountTargetId,
})
.pipe(Effect.map((r) => [...r.SecurityGroups].sort()));
expect(observed).toEqual(
[defaultSecurityGroupId, extraSecurityGroupId].sort(),
);
}),
{ timeout: 120_000 },
);
},
);
// Full teardown verification for the shared fixture is implicit: the mount
// target's delete waits until it is observed gone (its ENI must release
// before the security group and file system can delete), so a leaked mount
// target fails the afterAll destroy. Multi-AZ is gated — it adds another
// 1–3 minute create/delete cycle per AZ.
test.provider.skipIf(!process.env.AWS_TEST_EFS_MULTI_AZ)(
"creates one mount target per availability zone",
(stack) =>
Effect.gen(function* () {
yield* stack.destroy();
const vpc = yield* getDefaultVpc;
const subnets = yield* EC2.describeSubnets({
Filters: [
{ Name: "vpc-id", Values: [vpc.vpcId] },
{ Name: "default-for-az", Values: ["true"] },
],
});
const [subnetA, subnetB] = subnets.Subnets!.map((s) => s.SubnetId!);
const deployed = yield* stack.deploy(
Effect.gen(function* () {
const files = yield* AWS.EFS.FileSystem("MultiAzFiles");
const targetA = yield* AWS.EFS.MountTarget("MtA", {
fileSystemId: files.fileSystemId,
subnetId: subnetA,
});
const targetB = yield* AWS.EFS.MountTarget("MtB", {
fileSystemId: files.fileSystemId,
subnetId: subnetB,
});
return { files, targetA, targetB };
}),
);
expect(deployed.targetA.availabilityZoneName).not.toBe(
deployed.targetB.availabilityZoneName,
);
const observed = yield* efs.describeMountTargets({
FileSystemId: deployed.files.fileSystemId,
});
expect(observed.MountTargets).toHaveLength(2);
yield* stack.destroy();
const gone = yield* efs
.describeMountTargets({ FileSystemId: deployed.files.fileSystemId })
.pipe(
Effect.map((r) => (r.MountTargets ?? []).length === 0),
Effect.catchTag("FileSystemNotFound", () => Effect.succeed(true)),
Effect.repeat({
schedule: Schedule.fixed("5 seconds"),
until: (g) => g,
times: 36,
}),
);
expect(gone).toBe(true);
}).pipe(
Effect.tap(() => stack.destroy()),
Effect.onError(() => stack.destroy().pipe(Effect.ignore)),
),
{
tags: ["provider:aws", "provider:aws:ec2", "provider:aws:efs", "live"],
timeout: 600_000,
},
);