1import * as AWS from "@/AWS";
2import { KxDatabase, KxEnvironment } from "@/AWS/FinSpace";
3import { Key } from "@/AWS/KMS";
4import * as Test from "@/Test/Alchemy";
5import * as finspace from "@distilled.cloud/aws/finspace";
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
12// Ungated typed-error probes: prove the distilled error union carries the
13// not-found tags the Kx providers' read/delete paths depend on.
14test.provider(
15 "getKxEnvironment on a nonexistent environment fails with ResourceNotFoundException",
16 () =>
17 Effect.gen(function* () {
18 const error = yield* Effect.flip(
19 finspace.getKxEnvironment({
20 environmentId: "zzzzzzzzzzzzzzzzzzzzzzzzzz",
21 }),
22 );
23 expect(error._tag).toBe("ResourceNotFoundException");
24 }),
25 { tags: ["provider:aws", "provider:aws:finspace", "live"] },
26);
27
28test.provider(
29 "getKxDatabase on a nonexistent environment fails with ResourceNotFoundException",
30 () =>
31 Effect.gen(function* () {
32 const error = yield* Effect.flip(
33 finspace.getKxDatabase({
34 environmentId: "zzzzzzzzzzzzzzzzzzzzzzzzzz",
35 databaseName: "nodb",
36 }),
37 );
38 expect(error._tag).toBe("ResourceNotFoundException");
39 }),
40 { tags: ["provider:aws", "provider:aws:finspace", "live"] },
41);
42
43test.provider(
44 "getKxCluster on a nonexistent environment fails with ResourceNotFoundException",
45 () =>
46 Effect.gen(function* () {
47 const error = yield* Effect.flip(
48 finspace.getKxCluster({
49 environmentId: "zzzzzzzzzzzzzzzzzzzzzzzzzz",
50 clusterName: "nocluster",
51 }),
52 );
53 expect(error._tag).toBe("ResourceNotFoundException");
54 }),
55 { tags: ["provider:aws", "provider:aws:finspace", "live"] },
56);
57
58// Probes for the scaling-group / volume binding operations: prove the typed
59// not-found tag so runtime consumers can catch it without casts.
60test.provider(
61 "getKxScalingGroup on a nonexistent environment fails with ResourceNotFoundException",
62 () =>
63 Effect.gen(function* () {
64 const error = yield* Effect.flip(
65 finspace.getKxScalingGroup({
66 environmentId: "zzzzzzzzzzzzzzzzzzzzzzzzzz",
67 scalingGroupName: "nogroup",
68 }),
69 );
70 expect(error._tag).toBe("ResourceNotFoundException");
71 }),
72 { tags: ["provider:aws", "provider:aws:finspace", "live"] },
73);
74
75test.provider(
76 "getKxVolume on a nonexistent environment fails with ResourceNotFoundException",
77 () =>
78 Effect.gen(function* () {
79 const error = yield* Effect.flip(
80 finspace.getKxVolume({
81 environmentId: "zzzzzzzzzzzzzzzzzzzzzzzzzz",
82 volumeName: "novolume",
83 }),
84 );
85 expect(error._tag).toBe("ResourceNotFoundException");
86 }),
87 { tags: ["provider:aws", "provider:aws:finspace", "live"] },
88);
89
90// Deletion is verified as INITIATED (irreversible) or fully gone.
91const assertKxEnvironmentDeleting = (environmentId: string) =>
92 Effect.gen(function* () {
93 const status = yield* finspace.getKxEnvironment({ environmentId }).pipe(
94 Effect.map((r) => r.status ?? "gone"),
95 Effect.catchTag("ResourceNotFoundException", () =>
96 Effect.succeed("gone" as const),
97 ),
98 );
99 if (
100 status !== "gone" &&
101 status !== "DELETED" &&
102 status !== "DELETING" &&
103 status !== "DELETE_REQUESTED"
104 ) {
105 return yield* Effect.fail(
106 new Error(
107 `kdb environment '${environmentId}' still exists (${status})`,
108 ),
109 );
110 }
111 }).pipe(
112 Effect.retry({
113 schedule: Schedule.max([
114 Schedule.fixed("10 seconds"),
115 Schedule.recurs(18),
116 ]),
117 }),
118 );
119
120// A managed kdb environment takes tens of minutes to provision and FinSpace
121// is closed to non-onboarded accounts (CreateKxEnvironment rejects). The
122// full lifecycle is gated behind AWS_TEST_FINSPACE=1 and always destroys
123// what it created. KxCluster additionally needs a VPC and dedicated
124// capacity (~30+ min, billed per node-hour) — it is covered by the typed
125// probe above and exercised only in onboarded accounts.
126test.provider.skipIf(!process.env.AWS_TEST_FINSPACE)(
127 "create kdb environment + database, verify, update, destroy",
128 (stack) =>
129 Effect.gen(function* () {
130 yield* stack.destroy();
131
132 const { env, db } = yield* stack.deploy(
133 Effect.gen(function* () {
134 const key = yield* Key("KdbKey", {
135 description: "alchemy finspace kdb test key",
136 });
137 const env = yield* KxEnvironment("Kdb", {
138 kmsKeyId: key.keyArn,
139 description: "alchemy kdb test environment",
140 tags: { fixture: "finspace-kx" },
141 });
142 const db = yield* KxDatabase("Ticks", {
143 environmentId: env.environmentId,
144 description: "alchemy kdb test database",
145 tags: { fixture: "finspace-kx" },
146 });
147 return { env, db };
148 }),
149 );
150
151 expect(env.environmentId).toBeDefined();
152 expect(env.status).toBe("CREATED");
153 expect(db.databaseArn).toContain(":kxEnvironment/");
154
155 // Out-of-band verification via distilled.
156 const observedDb = yield* finspace.getKxDatabase({
157 environmentId: env.environmentId,
158 databaseName: db.databaseName,
159 });
160 expect(observedDb.databaseName).toBe(db.databaseName);
161
162 // Update the database description in place (no replacement).
163 const { db: updated } = yield* stack.deploy(
164 Effect.gen(function* () {
165 const key = yield* Key("KdbKey", {
166 description: "alchemy finspace kdb test key",
167 });
168 const env = yield* KxEnvironment("Kdb", {
169 kmsKeyId: key.keyArn,
170 description: "alchemy kdb test environment",
171 tags: { fixture: "finspace-kx" },
172 });
173 const db = yield* KxDatabase("Ticks", {
174 environmentId: env.environmentId,
175 description: "alchemy kdb test database (updated)",
176 tags: { fixture: "finspace-kx" },
177 });
178 return { env, db };
179 }),
180 );
181 expect(updated.databaseName).toBe(db.databaseName);
182 expect(updated.description).toBe("alchemy kdb test database (updated)");
183
184 // Destroy — environments bill while they exist — and verify deletion
185 // was initiated out-of-band.
186 const environmentId = env.environmentId;
187 yield* stack.destroy();
188 yield* assertKxEnvironmentDeleting(environmentId);
189 }),
190 // kdb environment create (tens of minutes) + database + delete, one test.
191 {
192 tags: ["provider:aws", "provider:aws:finspace", "provider:aws:kms", "live"],
193 timeout: 3_000_000,
194 },
195);