1import * as AWS from "@/AWS";
2import { JobDefinition } from "@/AWS/Batch/JobDefinition.ts";
3import { Role } from "@/AWS/IAM/Role.ts";
4import * as Test from "@/Test/Alchemy";
5import * as batch from "@distilled.cloud/aws/batch";
6import { expect } from "alchemy-test";
7import * as Effect from "effect/Effect";
8import * as Stream from "effect/Stream";
9
10const { test } = Test.make({ providers: AWS.providers() });
11
12const name = "alchemy-test-batch-jobdef";
13
14const activeRevisions = batch.describeJobDefinitions
15 .pages({ jobDefinitionName: name, status: "ACTIVE" })
16 .pipe(
17 Stream.runCollect,
18 Effect.map((pages) =>
19 Array.from(pages).flatMap((p) => p.jobDefinitions ?? []),
20 ),
21 );
22
23const jobDef = (command: string[]) =>
24 Effect.gen(function* () {
25 const executionRole = yield* Role("JobDefExecutionRole", {
26 assumeRolePolicyDocument: {
27 Version: "2012-10-17",
28 Statement: [
29 {
30 Effect: "Allow",
31 Principal: { Service: "ecs-tasks.amazonaws.com" },
32 Action: ["sts:AssumeRole"],
33 },
34 ],
35 },
36 managedPolicyArns: [
37 "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy",
38 ],
39 });
40 return yield* JobDefinition("EchoJobDef", {
41 jobDefinitionName: name,
42 image: "public.ecr.aws/docker/library/busybox:latest",
43 command,
44 executionRoleArn: executionRole.roleArn,
45 });
46 });
47
48test.provider(
49 "revision semantics: register, no-op redeploy, revise, deregister all",
50 (stack) =>
51 Effect.gen(function* () {
52 yield* stack.destroy();
53
54 // Register revision 1.
55 const created = yield* stack.deploy(jobDef(["echo", "one"]));
56 expect(created.jobDefinitionName).toBe(name);
57 const firstRevision = created.revision;
58 expect(firstRevision).toBeGreaterThanOrEqual(1);
59 expect(created.jobDefinitionArn).toContain(
60 `job-definition/${name}:${firstRevision}`,
61 );
62
63 // Identical content — no new revision registered.
64 const unchanged = yield* stack.deploy(jobDef(["echo", "one"]));
65 expect(unchanged.revision).toBe(firstRevision);
66 expect(yield* activeRevisions).toHaveLength(1);
67
68 // Changed command — a new immutable revision under the same family.
69 const revised = yield* stack.deploy(jobDef(["echo", "two"]));
70 expect(revised.revision).toBe(firstRevision + 1);
71 const revisions = yield* activeRevisions;
72 expect(revisions).toHaveLength(2);
73 const latest = revisions.find((d) => d.revision === revised.revision);
74 expect(latest?.containerProperties?.command).toEqual(["echo", "two"]);
75 expect(latest?.containerProperties?.resourceRequirements).toEqual(
76 expect.arrayContaining([
77 { type: "VCPU", value: "0.25" },
78 { type: "MEMORY", value: "512" },
79 ]),
80 );
81
82 // Destroy — EVERY active revision of the family is deregistered.
83 yield* stack.destroy();
84 expect(yield* activeRevisions).toHaveLength(0);
85 }),
86 {
87 tags: ["provider:aws", "provider:aws:batch", "provider:aws:iam", "live"],
88 timeout: 240_000,
89 },
90);