1import * as AWS from "@/AWS";
2import { Configuration } from "@/AWS/MQ";
3import * as Test from "@/Test/Alchemy";
4import * as mq from "@distilled.cloud/aws/mq";
5import { 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// Ungated typed-error probes: prove the distilled MQ error union carries the
12// not-found tag the providers' observe/read/delete paths depend on.
13test.provider(
14 "describeBroker on a nonexistent broker fails with NotFoundException",
15 () =>
16 Effect.gen(function* () {
17 const error = yield* Effect.flip(
18 mq.describeBroker({
19 BrokerId: "b-00000000-0000-0000-0000-000000000000",
20 }),
21 );
22 expect(error._tag).toBe("NotFoundException");
23 }),
24 { tags: ["provider:aws", "provider:aws:mq", "live"] },
25);
26
27test.provider(
28 "describeConfiguration on a nonexistent configuration fails with NotFoundException",
29 () =>
30 Effect.gen(function* () {
31 const error = yield* Effect.flip(
32 mq.describeConfiguration({
33 ConfigurationId: "c-00000000-0000-0000-0000-000000000000",
34 }),
35 );
36 expect(error._tag).toBe("NotFoundException");
37 }),
38 { tags: ["provider:aws", "provider:aws:mq", "live"] },
39);
40
41const assertConfigurationGone = (configurationId: string) =>
42 Effect.gen(function* () {
43 const status = yield* mq
44 .describeConfiguration({ ConfigurationId: configurationId })
45 .pipe(
46 Effect.map(() => "PRESENT" as const),
47 Effect.catchTag("NotFoundException", () =>
48 Effect.succeed("GONE" as const),
49 ),
50 );
51 if (status !== "GONE") {
52 return yield* Effect.fail(
53 new Error(`MQ configuration ${configurationId} still exists`),
54 );
55 }
56 }).pipe(
57 Effect.retry({
58 schedule: Schedule.max([Schedule.fixed("3 seconds"), Schedule.recurs(8)]),
59 }),
60 );
61
62const configDataV1 = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
63<broker xmlns="http://activemq.apache.org/schema/core">
64 <destinationPolicy>
65 <policyMap>
66 <policyEntries>
67 <policyEntry topic=">">
68 <pendingMessageLimitStrategy>
69 <constantPendingMessageLimitStrategy limit="1000"/>
70 </pendingMessageLimitStrategy>
71 </policyEntry>
72 </policyEntries>
73 </policyMap>
74 </destinationPolicy>
75</broker>
76`;
77
78const configDataV2 = configDataV1.replace('limit="1000"', 'limit="2000"');
79
80// A configuration is cheap and fast (no broker provisioning), so the full
81// lifecycle runs ungated: create publishes revision 2 (custom data over the
82// engine default), a data change publishes revision 3, then destroy.
83test.provider(
84 "create an ActiveMQ configuration, publish revisions on data change, destroy",
85 (stack) =>
86 Effect.gen(function* () {
87 yield* stack.destroy();
88
89 // Pick a valid ActiveMQ engine version from the live API so the test
90 // never breaks when AWS retires a hardcoded version.
91 const engines = yield* mq.describeBrokerEngineTypes({
92 EngineType: "ACTIVEMQ",
93 });
94 const engineVersion =
95 engines.BrokerEngineTypes?.[0]?.EngineVersions?.[0]?.Name;
96 expect(engineVersion).toBeDefined();
97
98 const created = yield* stack.deploy(
99 Effect.gen(function* () {
100 return yield* Configuration("BrokerConfig", {
101 engineType: "ACTIVEMQ",
102 engineVersion,
103 description: "alchemy test config",
104 data: configDataV1,
105 tags: { team: "messaging" },
106 });
107 }),
108 );
109
110 expect(created.configurationArn).toContain(":configuration:");
111 // MQ echoes the display casing ("ActiveMQ") rather than the request enum.
112 expect(created.engineType.toUpperCase()).toBe("ACTIVEMQ");
113 // createConfiguration seeds revision 1; publishing custom data -> 2.
114 expect(created.configurationRevision).toBeGreaterThanOrEqual(2);
115
116 // Out-of-band verification via distilled.
117 const observed = yield* mq.describeConfiguration({
118 ConfigurationId: created.configurationId,
119 });
120 expect(observed.Name).toBe(created.configurationName);
121 expect(observed.Tags?.team).toBe("messaging");
122
123 // A data change publishes a new revision.
124 const updated = yield* stack.deploy(
125 Effect.gen(function* () {
126 return yield* Configuration("BrokerConfig", {
127 engineType: "ACTIVEMQ",
128 engineVersion,
129 description: "alchemy test config v2",
130 data: configDataV2,
131 tags: { team: "messaging" },
132 });
133 }),
134 );
135 expect(updated.configurationId).toBe(created.configurationId);
136 expect(updated.configurationRevision).toBeGreaterThan(
137 created.configurationRevision,
138 );
139
140 yield* stack.destroy();
141 yield* assertConfigurationGone(created.configurationId);
142 }),
143 { tags: ["provider:aws", "provider:aws:mq", "live"], timeout: 180_000 },
144);