1import * as AWS from "@/AWS";
2import { Channel, StreamKey } from "@/AWS/IVS";
3import * as Test from "@/Test/Alchemy";
4import * as ivs from "@distilled.cloud/aws/ivs";
5import { expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7import * as Redacted from "effect/Redacted";
8import * as Schedule from "effect/Schedule";
9
10const { test } = Test.make({ providers: AWS.providers() });
11
12const assertStreamKeyGone = (arn: string) =>
13 Effect.gen(function* () {
14 const streamKey = yield* ivs.getStreamKey({ arn }).pipe(
15 Effect.map((r) => r.streamKey),
16 Effect.catchTag("ResourceNotFoundException", () =>
17 Effect.succeed(undefined),
18 ),
19 );
20 if (streamKey !== undefined) {
21 return yield* Effect.fail(new Error(`stream key '${arn}' still exists`));
22 }
23 }).pipe(
24 Effect.retry({
25 schedule: Schedule.max([Schedule.fixed("3 seconds"), Schedule.recurs(8)]),
26 }),
27 );
28
29// CreateChannel auto-provisions a channel's single allowed stream key, so
30// this also covers the adopt-the-default-key path of the provider.
31test.provider(
32 "manage the stream key of a channel",
33 (stack) =>
34 Effect.gen(function* () {
35 yield* stack.destroy();
36
37 const deployed = yield* stack.deploy(
38 Effect.gen(function* () {
39 const channel = yield* Channel("KeyedChannel", {
40 channelName: "alchemy-test-ivs-streamkey-channel",
41 type: "BASIC",
42 });
43 const streamKey = yield* StreamKey("Key", {
44 channelArn: channel.channelArn,
45 tags: { fixture: "ivs-stream-key" },
46 });
47 return { channel, streamKey };
48 }),
49 );
50
51 expect(deployed.streamKey.streamKeyArn).toContain(":stream-key/");
52 expect(deployed.streamKey.channelArn).toBe(deployed.channel.channelArn);
53 expect(deployed.streamKey.value).toBeDefined();
54 expect(Redacted.value(deployed.streamKey.value!)).toMatch(/^sk_/);
55
56 // Out-of-band verification via distilled: the key is tagged ours
57 // (the provider adopted the channel's auto-created key or created
58 // one — either way tags must have converged).
59 const observed = yield* ivs.getStreamKey({
60 arn: deployed.streamKey.streamKeyArn,
61 });
62 expect(observed.streamKey?.channelArn).toBe(deployed.channel.channelArn);
63 expect(observed.streamKey?.tags?.fixture).toBe("ivs-stream-key");
64 expect(observed.streamKey?.tags?.["alchemy::id"]).toBe("Key");
65
66 // The channel must have exactly one key (we adopted, not duplicated).
67 const listed = yield* ivs.listStreamKeys({
68 channelArn: deployed.channel.channelArn,
69 });
70 expect(listed.streamKeys.length).toBe(1);
71
72 // No-op redeploy keeps the same key.
73 const redeployed = yield* stack.deploy(
74 Effect.gen(function* () {
75 const channel = yield* Channel("KeyedChannel", {
76 channelName: "alchemy-test-ivs-streamkey-channel",
77 type: "BASIC",
78 });
79 const streamKey = yield* StreamKey("Key", {
80 channelArn: channel.channelArn,
81 tags: { fixture: "ivs-stream-key" },
82 });
83 return { channel, streamKey };
84 }),
85 );
86 expect(redeployed.streamKey.streamKeyArn).toBe(
87 deployed.streamKey.streamKeyArn,
88 );
89
90 // Destroy and verify out-of-band with a typed wait-until-gone.
91 yield* stack.destroy();
92 yield* assertStreamKeyGone(deployed.streamKey.streamKeyArn);
93 }),
94 { tags: ["provider:aws", "provider:aws:ivs", "live"], timeout: 240_000 },
95);