1import * as AWS from "@/AWS";
2import { TopicRule } from "@/AWS/IoT";
3import * as Test from "@/Test/Alchemy";
4import * as iot from "@distilled.cloud/aws/iot";
5import { describe, expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7import * as Schedule from "effect/Schedule";
8
9const { test } = Test.make({ providers: AWS.providers() });
10
11// A syntactically valid Lambda ARN for a function that need not exist —
12// createTopicRule validates the ARN shape, not existence.
13const FAKE_LAMBDA_ARN =
14 "arn:aws:lambda:us-west-2:391965393224:function:alchemy-iot-topicrule-test";
15
16const assertRuleGone = (ruleName: string) =>
17 iot.getTopicRule({ ruleName }).pipe(
18 Effect.flatMap(() =>
19 Effect.fail(new Error(`topic rule ${ruleName} still exists`)),
20 ),
21 Effect.catchTag("TopicRuleNotFound", () => Effect.void),
22 Effect.retry({
23 while: (e) => e instanceof Error,
24 schedule: Schedule.max([
25 Schedule.fixed("2 seconds"),
26 Schedule.recurs(10),
27 ]),
28 }),
29 );
30
31describe.sequential(
32 "AWS.IoT.TopicRule",
33 { tags: ["provider:aws", "provider:aws:iot", "live"] },
34 () => {
35 test.provider(
36 "creates, updates the SQL in place, and deletes a topic rule",
37 (stack) =>
38 Effect.gen(function* () {
39 yield* stack.destroy();
40
41 const created = yield* stack.deploy(
42 Effect.gen(function* () {
43 const rule = yield* TopicRule("Rule", {
44 sql: "SELECT * FROM 'alchemy/iot/test/v1'",
45 actions: [{ lambda: { functionArn: FAKE_LAMBDA_ARN } }],
46 });
47 return { ruleName: rule.ruleName, ruleArn: rule.ruleArn };
48 }),
49 );
50
51 // Verify out-of-band.
52 const observed = yield* iot.getTopicRule({
53 ruleName: created.ruleName,
54 });
55 expect(observed.rule?.sql).toEqual(
56 "SELECT * FROM 'alchemy/iot/test/v1'",
57 );
58
59 // Update the SQL — same name, so this is an in-place replaceTopicRule.
60 yield* stack.deploy(
61 Effect.gen(function* () {
62 yield* TopicRule("Rule", {
63 sql: "SELECT temperature FROM 'alchemy/iot/test/v2'",
64 actions: [{ lambda: { functionArn: FAKE_LAMBDA_ARN } }],
65 });
66 }),
67 );
68 const updated = yield* iot.getTopicRule({
69 ruleName: created.ruleName,
70 });
71 expect(updated.rule?.sql).toEqual(
72 "SELECT temperature FROM 'alchemy/iot/test/v2'",
73 );
74
75 yield* stack.destroy();
76 yield* assertRuleGone(created.ruleName);
77 }),
78 { timeout: 180_000 },
79 );
80
81 test.provider(
82 "getTopicRule on a missing rule surfaces the typed TopicRuleNotFound tag",
83 () =>
84 Effect.gen(function* () {
85 const result = yield* iot
86 .getTopicRule({ ruleName: "alchemy_definitely_missing_rule_xyz" })
87 .pipe(Effect.flip);
88 expect(result._tag).toEqual("TopicRuleNotFound");
89 }),
90 { timeout: 60_000 },
91 );
92 },
93);