1import * as AWS from "@/AWS";
2import { LoggingConfiguration, Room } from "@/AWS/IVSChat";
3import { LogGroup } from "@/AWS/Logs";
4import * as Test from "@/Test/Alchemy";
5import * as ivschat from "@distilled.cloud/aws/ivschat";
6import { expect } from "alchemy-test";
7import * as Effect from "effect/Effect";
8import * as Schedule from "effect/Schedule";
9
10const { test } = Test.make({ providers: AWS.providers() });
11
12const assertLoggingConfigurationGone = (arn: string) =>
13 Effect.gen(function* () {
14 const config = yield* ivschat
15 .getLoggingConfiguration({ identifier: arn })
16 .pipe(
17 Effect.map((r) => r.arn),
18 Effect.catchTag("ResourceNotFoundException", () =>
19 Effect.succeed(undefined),
20 ),
21 );
22 if (config !== undefined) {
23 return yield* Effect.fail(
24 new Error(`logging configuration '${arn}' still exists`),
25 );
26 }
27 }).pipe(
28 Effect.retry({
29 schedule: Schedule.max([Schedule.fixed("3 seconds"), Schedule.recurs(8)]),
30 }),
31 );
32
33// Covers create (CloudWatch Logs destination, waits for ACTIVE),
34// attachment to a room, no-op, and destroy.
35test.provider(
36 "create, attach, and destroy an IVS Chat logging configuration",
37 (stack) =>
38 Effect.gen(function* () {
39 yield* stack.destroy();
40
41 const program = Effect.gen(function* () {
42 const logGroup = yield* LogGroup("ChatLogGroup", {
43 logGroupName: "/alchemy-test/ivschat-logging",
44 retention: "1 day",
45 });
46 const logging = yield* LoggingConfiguration("ChatLogs", {
47 loggingConfigurationName: "alchemy-test-ivschat-logging",
48 destinationConfiguration: {
49 cloudWatchLogs: { logGroupName: logGroup.logGroupName },
50 },
51 tags: { fixture: "ivschat-logging" },
52 });
53 const room = yield* Room("LoggedChat", {
54 roomName: "alchemy-test-ivschat-logged-room",
55 loggingConfigurationIdentifiers: [logging.loggingConfigurationArn],
56 });
57 return { logging, room };
58 });
59
60 // Create.
61 const created = yield* stack.deploy(program);
62 expect(created.logging.loggingConfigurationName).toBe(
63 "alchemy-test-ivschat-logging",
64 );
65 expect(created.logging.loggingConfigurationArn).toContain(
66 ":logging-configuration/",
67 );
68 expect(created.logging.loggingConfigurationId).toBeDefined();
69 expect(created.logging.state).toBe("ACTIVE");
70
71 // Out-of-band verification via distilled.
72 const observed = yield* ivschat.getLoggingConfiguration({
73 identifier: created.logging.loggingConfigurationArn,
74 });
75 expect(observed.state).toBe("ACTIVE");
76 expect(
77 observed.destinationConfiguration &&
78 "cloudWatchLogs" in observed.destinationConfiguration
79 ? observed.destinationConfiguration.cloudWatchLogs?.logGroupName
80 : undefined,
81 ).toBe("/alchemy-test/ivschat-logging");
82 expect(observed.tags?.["alchemy::id"]).toBe("ChatLogs");
83
84 // The room is attached to the logging configuration.
85 const observedRoom = yield* ivschat.getRoom({
86 identifier: created.room.roomArn,
87 });
88 expect(observedRoom.loggingConfigurationIdentifiers).toContain(
89 created.logging.loggingConfigurationArn,
90 );
91
92 // No-op redeploy keeps the same configuration.
93 const noop = yield* stack.deploy(program);
94 expect(noop.logging.loggingConfigurationArn).toBe(
95 created.logging.loggingConfigurationArn,
96 );
97
98 // Destroy and verify out-of-band with a typed wait-until-gone.
99 yield* stack.destroy();
100 yield* assertLoggingConfigurationGone(
101 created.logging.loggingConfigurationArn,
102 );
103 }),
104 {
105 tags: ["provider:aws", "provider:aws:ivschat", "provider:aws:logs", "live"],
106 timeout: 240_000,
107 },
108);