1import * as AWS from "@/AWS";
2import { Application, ApplicationSnapshot } from "@/AWS/KinesisAnalyticsV2";
3import * as Test from "@/Test/Alchemy";
4import * as analytics from "@distilled.cloud/aws/kinesis-analytics-v2";
5import { describe, expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7
8const { test } = Test.make({ providers: AWS.providers() });
9
10/**
11 * Snapshots require a RUNNING application, and starting an application
12 * requires a REAL Flink application jar (a placeholder zip passes creation
13 * but the Flink job submission fails and the start rolls back). There is no
14 * way to fabricate a runnable jar in this repo, so the live lifecycle is
15 * gated behind:
16 *
17 * AWS_TEST_FLINK_START=1
18 * AWS_TEST_FLINK_JAR_BUCKET_ARN=arn:aws:s3:::my-bucket (bucket holding the jar)
19 * AWS_TEST_FLINK_JAR_KEY=jars/my-flink-app.jar (key of a runnable jar)
20 *
21 * The jar must target the FLINK-1_20 runtime. The ungated typed-error probe
22 * for snapshots (describeApplicationSnapshot on a missing snapshot →
23 * ResourceNotFoundException) lives in Application.test.ts.
24 */
25const gated =
26 !process.env.AWS_TEST_FLINK_START ||
27 !process.env.AWS_TEST_FLINK_JAR_BUCKET_ARN ||
28 !process.env.AWS_TEST_FLINK_JAR_KEY;
29
30describe.skipIf(gated)(
31 "AWS.KinesisAnalyticsV2.ApplicationSnapshot",
32 { tags: ["provider:aws", "provider:aws:kinesisanalyticsv2", "live"] },
33 () => {
34 test.provider(
35 "start application, snapshot it, destroy",
36 (stack) =>
37 Effect.gen(function* () {
38 yield* stack.destroy();
39
40 const deployed = yield* stack.deploy(
41 Effect.gen(function* () {
42 const app = yield* Application("SnapshottedFlinkApp", {
43 runtimeEnvironment: "FLINK-1_20",
44 code: {
45 bucketArn: process.env.AWS_TEST_FLINK_JAR_BUCKET_ARN!,
46 fileKey: process.env.AWS_TEST_FLINK_JAR_KEY!,
47 },
48 snapshotsEnabled: true,
49 start: true,
50 });
51 const snapshot = yield* ApplicationSnapshot("Checkpoint", {
52 applicationName: app.applicationName,
53 });
54 return { app, snapshot };
55 }),
56 );
57
58 expect(deployed.app.applicationStatus).toEqual("RUNNING");
59 expect(deployed.snapshot.snapshotStatus).toEqual("READY");
60 expect(deployed.snapshot.applicationName).toEqual(
61 deployed.app.applicationName,
62 );
63
64 // Out-of-band verification via distilled.
65 const described = yield* analytics.describeApplicationSnapshot({
66 ApplicationName: deployed.app.applicationName,
67 SnapshotName: deployed.snapshot.snapshotName,
68 });
69 expect(described.SnapshotDetails.SnapshotStatus).toEqual("READY");
70
71 yield* stack.destroy();
72
73 // The snapshot is deleted with the stack (before the application).
74 const gone = yield* analytics
75 .describeApplication({
76 ApplicationName: deployed.app.applicationName,
77 })
78 .pipe(Effect.flip);
79 expect(gone._tag).toEqual("ResourceNotFoundException");
80 }),
81 { timeout: 1_140_000 },
82 );
83 },
84);