1import * as AWS from "@/AWS";
2import { Activity } from "@/AWS/StepFunctions";
3import * as Test from "@/Test/Alchemy";
4import * as sfn from "@distilled.cloud/aws/sfn";
5import { expect } from "alchemy-test";
6import * as Data from "effect/Data";
7import * as Effect from "effect/Effect";
8import * as Schedule from "effect/Schedule";
9
10const { test } = Test.make({ providers: AWS.providers() });
11
12class ActivityStillExists extends Data.TaggedError("ActivityStillExists")<{
13 readonly activityArn: string;
14}> {}
15
16const assertActivityDeleted = (activityArn: string) =>
17 sfn.describeActivity({ activityArn }).pipe(
18 Effect.flatMap(() => Effect.fail(new ActivityStillExists({ activityArn }))),
19 Effect.catchTag("ActivityDoesNotExist", () => Effect.void),
20 Effect.retry({
21 while: (e) => e._tag === "ActivityStillExists",
22 schedule: Schedule.max([
23 Schedule.fixed("2 seconds"),
24 Schedule.recurs(10),
25 ]),
26 }),
27 );
28
29test.provider(
30 "create, update tags, destroy activity",
31 (stack) =>
32 Effect.gen(function* () {
33 const activity = yield* stack.deploy(
34 Effect.gen(function* () {
35 return yield* Activity("Worker", {
36 tags: { Environment: "test" },
37 });
38 }),
39 );
40
41 expect(activity.activityArn).toContain(":activity:");
42 expect(activity.activityName).toBeDefined();
43
44 // out-of-band verification via distilled
45 const created = yield* sfn.describeActivity({
46 activityArn: activity.activityArn,
47 });
48 expect(created.name).toBe(activity.activityName);
49
50 const tags = yield* sfn.listTagsForResource({
51 resourceArn: activity.activityArn,
52 });
53 const tagRecord = Object.fromEntries(
54 (tags.tags ?? []).map((t) => [t.key, t.value]),
55 );
56 expect(tagRecord.Environment).toBe("test");
57 expect(tagRecord["alchemy::id"]).toBe("Worker");
58
59 // tag update converges in place
60 const updated = yield* stack.deploy(
61 Effect.gen(function* () {
62 return yield* Activity("Worker", {
63 tags: { Environment: "test", Extra: "1" },
64 });
65 }),
66 );
67 expect(updated.activityArn).toBe(activity.activityArn);
68 const afterUpdate = yield* sfn.listTagsForResource({
69 resourceArn: activity.activityArn,
70 });
71 expect(
72 Object.fromEntries(
73 (afterUpdate.tags ?? []).map((t) => [t.key, t.value]),
74 ).Extra,
75 ).toBe("1");
76
77 yield* stack.destroy();
78 yield* assertActivityDeleted(activity.activityArn);
79 }),
80 {
81 tags: ["provider:aws", "provider:aws:stepfunctions", "live"],
82 timeout: 120_000,
83 },
84);