1import * as AWS from "@/AWS";
2import { ConfigurationSet } from "@/AWS/PinpointSMSVoiceV2";
3import * as Test from "@/Test/Alchemy";
4import * as smsvoice from "@distilled.cloud/aws/pinpoint-sms-voice-v2";
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 probe: proves the distilled error union carries the
12// not-found tag this provider's read/observe paths depend on.
13test.provider(
14 "describeConfigurationSets on a nonexistent name fails with ResourceNotFoundException",
15 () =>
16 Effect.gen(function* () {
17 const error = yield* Effect.flip(
18 smsvoice.describeConfigurationSets({
19 ConfigurationSetNames: ["alchemy-nonexistent-config-set-probe"],
20 }),
21 );
22 expect(error._tag).toBe("ResourceNotFoundException");
23 }),
24 {
25 tags: ["provider:aws", "provider:aws:pinpointsmsvoicev2", "live"],
26 timeout: 60_000,
27 },
28);
29
30const getConfigSet = (name: string) =>
31 smsvoice.describeConfigurationSets({ ConfigurationSetNames: [name] }).pipe(
32 Effect.map((r) => r.ConfigurationSets?.[0]),
33 Effect.catchTag("ResourceNotFoundException", () =>
34 Effect.succeed(undefined),
35 ),
36 );
37
38const assertConfigSetGone = (name: string) =>
39 Effect.gen(function* () {
40 const found = yield* getConfigSet(name);
41 if (found !== undefined) {
42 return yield* Effect.fail(
43 new Error(`configuration set '${name}' still exists`),
44 );
45 }
46 }).pipe(
47 Effect.retry({
48 schedule: Schedule.max([Schedule.fixed("3 seconds"), Schedule.recurs(8)]),
49 }),
50 );
51
52// Configuration sets are free and provision synchronously — the full
53// lifecycle (create, no-op, update-in-place, destroy) runs ungated.
54test.provider(
55 "create, update, and destroy a configuration set",
56 (stack) =>
57 Effect.gen(function* () {
58 yield* stack.destroy();
59
60 const props = {
61 configurationSetName: "alchemy-test-smsvoice-config-set",
62 tags: { fixture: "smsvoice-config-set" },
63 };
64
65 // Create.
66 const created = yield* stack.deploy(ConfigurationSet("Config", props));
67 expect(created.configurationSetName).toBe(
68 "alchemy-test-smsvoice-config-set",
69 );
70 expect(created.configurationSetArn).toContain(":configuration-set/");
71
72 // Out-of-band verification via distilled.
73 const observed = yield* getConfigSet(created.configurationSetName);
74 expect(observed?.ConfigurationSetName).toBe(created.configurationSetName);
75 expect(observed?.DefaultMessageType).toBeUndefined();
76 const tags = yield* smsvoice.listTagsForResource({
77 ResourceArn: created.configurationSetArn,
78 });
79 const tagRecord = Object.fromEntries(
80 (tags.Tags ?? []).map((t) => [t.Key, t.Value]),
81 );
82 expect(tagRecord.fixture).toBe("smsvoice-config-set");
83 expect(tagRecord["alchemy::id"]).toBe("Config");
84
85 // No-op redeploy keeps the same configuration set.
86 const noop = yield* stack.deploy(ConfigurationSet("Config", props));
87 expect(noop.configurationSetArn).toBe(created.configurationSetArn);
88
89 // Update in place — set a default message type and change a tag; the
90 // ARN must not change.
91 const updated = yield* stack.deploy(
92 ConfigurationSet("Config", {
93 ...props,
94 defaultMessageType: "TRANSACTIONAL",
95 tags: { fixture: "smsvoice-config-set-v2" },
96 }),
97 );
98 expect(updated.configurationSetArn).toBe(created.configurationSetArn);
99 const reobserved = yield* getConfigSet(created.configurationSetName);
100 expect(reobserved?.DefaultMessageType).toBe("TRANSACTIONAL");
101 const retags = yield* smsvoice.listTagsForResource({
102 ResourceArn: created.configurationSetArn,
103 });
104 expect(
105 Object.fromEntries((retags.Tags ?? []).map((t) => [t.Key, t.Value]))
106 .fixture,
107 ).toBe("smsvoice-config-set-v2");
108
109 // Removing the prop clears the default message type.
110 yield* stack.deploy(ConfigurationSet("Config", props));
111 const cleared = yield* getConfigSet(created.configurationSetName);
112 expect(cleared?.DefaultMessageType).toBeUndefined();
113
114 // Destroy and verify out-of-band with a typed wait-until-gone.
115 yield* stack.destroy();
116 yield* assertConfigSetGone(created.configurationSetName);
117 }),
118 {
119 tags: ["provider:aws", "provider:aws:pinpointsmsvoicev2", "live"],
120 timeout: 240_000,
121 },
122);