t3-code-android-nightly/.repos/alchemy-effect/packages/alchemy/test/AWS/Lambda/Alias.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

252 lines
7.7 KiB
TypeScript

import * as AWS from "@/AWS";
import * as Provider from "@/Provider";
import * as Test from "@/Test/Alchemy";
import * as Lambda from "@distilled.cloud/aws/lambda";
import { expect } from "alchemy-test";
import * as Effect from "effect/Effect";
import * as Schedule from "effect/Schedule";
import { fileURLToPath } from "node:url";
const timeoutHandlerPath = fileURLToPath(
new URL("./timeout-handler.ts", import.meta.url),
);
const { test } = Test.make({ providers: AWS.providers() });
test.provider(
"create, update, list, replace, delete alias",
(stack) =>
Effect.gen(function* () {
yield* stack.destroy();
const program = ({
envVersion,
alias,
}: {
envVersion: string;
alias?: {
aliasName: string;
description?: string;
routingConfig?: Lambda.AliasRoutingConfiguration;
};
}) =>
Effect.gen(function* () {
const fn = yield* AWS.Lambda.Function("AliasFn", {
main: timeoutHandlerPath,
handler: "handler",
isExternal: true,
functionUrl: false,
env: {
VERSION: envVersion,
},
});
const version = yield* AWS.Lambda.Version("AliasVersion", {
function: fn,
});
const live = alias
? yield* AWS.Lambda.Alias("LiveAlias", {
version,
aliasName: alias.aliasName,
description: alias.description,
routingConfig: alias.routingConfig,
})
: undefined;
return { fn, version, live };
});
const initial = yield* stack.deploy(program({ envVersion: "1" }));
const version1 = initial.version.version;
// --- create ---
const created = yield* stack.deploy(
program({
envVersion: "1",
alias: {
aliasName: "live",
description: "live v1",
},
}),
);
const createdAlias = created.live!;
expect(createdAlias.aliasName).toBe("live");
expect(createdAlias.functionVersion).toBe(version1);
expect(createdAlias.invokeArn).toContain(createdAlias.aliasArn);
// Assert the alias actually exists in the cloud.
const liveV1 = yield* getAliasOrUndefined(
created.fn.functionName,
"live",
);
expect(liveV1).toBeDefined();
expect(liveV1!.AliasArn).toBe(createdAlias.aliasArn);
expect(liveV1!.FunctionVersion).toBe(version1);
expect(liveV1!.Description).toBe("live v1");
expect(liveV1!.RoutingConfig?.AdditionalVersionWeights ?? {}).toEqual({});
// --- update (function version + weighted routing + description) ---
const updated = yield* stack.deploy(
program({
envVersion: "2",
alias: {
aliasName: "live",
description: "weighted live",
routingConfig: {
AdditionalVersionWeights: {
[version1]: 0.25,
},
},
},
}),
);
const version2 = updated.version.version;
const updatedAlias = updated.live!;
// Updating in place must keep the same ARN (not a replacement).
expect(updatedAlias.aliasArn).toBe(createdAlias.aliasArn);
expect(updatedAlias.functionVersion).toBe(version2);
expect(updatedAlias.description).toBe("weighted live");
expect(updatedAlias.routingConfig).toEqual({
AdditionalVersionWeights: {
[version1]: 0.25,
},
});
// Assert the cloud reflects the update.
const liveV2 = yield* getAliasOrUndefined(
updated.fn.functionName,
"live",
);
expect(liveV2).toBeDefined();
expect(liveV2!.AliasArn).toBe(createdAlias.aliasArn);
expect(liveV2!.FunctionVersion).toBe(version2);
expect(liveV2!.Description).toBe("weighted live");
expect(liveV2!.RoutingConfig?.AdditionalVersionWeights).toEqual({
[version1]: 0.25,
});
// --- update (clear description + routing) ---
const cleared = yield* stack.deploy(
program({
envVersion: "2",
alias: {
aliasName: "live",
},
}),
);
const clearedAlias = cleared.live!;
expect(clearedAlias.aliasArn).toBe(createdAlias.aliasArn);
expect(clearedAlias.functionVersion).toBe(version2);
expect(clearedAlias.description).toBeUndefined();
expect(clearedAlias.routingConfig).toBeUndefined();
// Assert the cloud cleared description and routing.
const liveCleared = yield* getAliasOrUndefined(
cleared.fn.functionName,
"live",
);
expect(liveCleared).toBeDefined();
expect(liveCleared!.Description ?? "").toBe("");
expect(
liveCleared!.RoutingConfig?.AdditionalVersionWeights ?? {},
).toEqual({});
// --- replace (rename the alias) ---
// The provider's `diff` flags an aliasName change as a replacement, so a
// brand new alias is created and the old one deleted.
const replaced = yield* stack.deploy(
program({
envVersion: "2",
alias: {
aliasName: "stable",
description: "renamed",
},
}),
);
const replacedAlias = replaced.live!;
expect(replacedAlias.aliasName).toBe("stable");
// A replacement must mint a new ARN.
expect(replacedAlias.aliasArn).not.toBe(createdAlias.aliasArn);
expect(replacedAlias.functionVersion).toBe(version2);
expect(replacedAlias.description).toBe("renamed");
// The new alias exists in the cloud...
const stableAlias = yield* getAliasOrUndefined(
replaced.fn.functionName,
"stable",
);
expect(stableAlias).toBeDefined();
expect(stableAlias!.AliasArn).toBe(replacedAlias.aliasArn);
expect(stableAlias!.FunctionVersion).toBe(version2);
expect(stableAlias!.Description).toBe("renamed");
// ...and the old one was deleted as part of the replacement.
const oldLive = yield* getAliasOrUndefined(
replaced.fn.functionName,
"live",
);
expect(oldLive).toBeUndefined();
// --- list ---
const provider = yield* Provider.findProvider(AWS.Lambda.Alias);
const aliases = yield* provider.list();
expect(
aliases.some(
(alias) =>
alias.functionName === replaced.fn.functionName &&
alias.aliasName === "stable",
),
).toBe(true);
// --- delete ---
yield* stack.destroy();
const afterDestroy = yield* getAliasOrUndefined(
replaced.fn.functionName,
"stable",
);
expect(afterDestroy).toBeUndefined();
// ...and the host function itself is gone (bounded retry to ride out
// delete propagation).
yield* Lambda.getFunction({
FunctionName: replaced.fn.functionName,
}).pipe(
Effect.flatMap(() =>
Effect.fail(
new Error(`Function ${replaced.fn.functionName} still exists`),
),
),
Effect.catchTag("ResourceNotFoundException", () => Effect.void),
Effect.retry({
schedule: Schedule.max([
Schedule.exponential(500),
Schedule.recurs(8),
]),
}),
);
}).pipe(
Effect.tap(() => stack.destroy()),
Effect.onError(() => stack.destroy().pipe(Effect.ignore)),
),
{ tags: ["provider:aws", "provider:aws:lambda", "live"], timeout: 360_000 },
);
const getAliasOrUndefined = Effect.fn(function* (
functionName: string,
name: string,
) {
return yield* Lambda.getAlias({
FunctionName: functionName,
Name: name,
}).pipe(
Effect.catchTag("ResourceNotFoundException", () =>
Effect.succeed(undefined),
),
);
});