1import * as AWS from "@/AWS";
2import { Environment } from "@/AWS/FinSpace";
3import * as Test from "@/Test/Alchemy";
4import * as finspace from "@distilled.cloud/aws/finspace";
5import * as finspaceData from "@distilled.cloud/aws/finspace-data";
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 probe: prove the distilled error union carries the
13// not-found tag the Environment provider's read/delete paths depend on.
14// (environmentId must match ^[a-zA-Z0-9]{1,26}$ — malformed ids fail earlier
15// with ValidationException.)
16test.provider(
17 "getEnvironment on a nonexistent environment fails with ResourceNotFoundException",
18 () =>
19 Effect.gen(function* () {
20 const error = yield* Effect.flip(
21 finspace.getEnvironment({
22 environmentId: "zzzzzzzzzzzzzzzzzzzzzzzzzz",
23 }),
24 );
25 expect(error._tag).toBe("ResourceNotFoundException");
26 }),
27 { tags: ["provider:aws", "provider:aws:finspace", "live"] },
28);
29
30// Ungated typed-error probe for the finspace-data (data-plane) SDK. The data
31// API is environment-scoped and this account has no FinSpace environment, so
32// it rejects with a plain-text 403 body ("Failed to retrieve environment").
33// This proves the distilled rest-json fallback turns that into the typed
34// AccessDeniedException instead of a ParseError.
35test.provider(
36 "finspace-data getDataset without an environment fails with AccessDeniedException",
37 () =>
38 Effect.gen(function* () {
39 const error = yield* Effect.flip(
40 finspaceData.getDataset({ datasetId: "zzzzzzzzzzzzzzzzzzzzzzzzzz" }),
41 );
42 expect(error._tag).toBe("AccessDeniedException");
43 }),
44 { tags: ["provider:aws", "provider:aws:finspace", "live"] },
45);
46
47// Deletion is verified as INITIATED (irreversible) or fully gone — full
48// disappearance takes many more minutes server-side.
49const assertEnvironmentDeleting = (environmentId: string) =>
50 Effect.gen(function* () {
51 const status = yield* finspace.getEnvironment({ environmentId }).pipe(
52 Effect.map((r) => r.environment?.status ?? "gone"),
53 Effect.catchTag("ResourceNotFoundException", () =>
54 Effect.succeed("gone" as const),
55 ),
56 );
57 if (
58 status !== "gone" &&
59 status !== "DELETED" &&
60 status !== "DELETING" &&
61 status !== "DELETE_REQUESTED"
62 ) {
63 return yield* Effect.fail(
64 new Error(`environment '${environmentId}' still exists (${status})`),
65 );
66 }
67 }).pipe(
68 Effect.retry({
69 schedule: Schedule.max([
70 Schedule.fixed("10 seconds"),
71 Schedule.recurs(18),
72 ]),
73 }),
74 );
75
76// A FinSpace environment takes ~20 minutes to provision, bills while it
77// exists, and FinSpace is closed to non-onboarded accounts. The full
78// lifecycle is gated behind AWS_TEST_FINSPACE=1 and always destroys what it
79// created.
80test.provider.skipIf(!process.env.AWS_TEST_FINSPACE)(
81 "create FinSpace environment, verify, update, destroy",
82 (stack) =>
83 Effect.gen(function* () {
84 yield* stack.destroy();
85
86 const { env } = yield* stack.deploy(
87 Effect.gen(function* () {
88 const env = yield* Environment("Analytics", {
89 description: "alchemy finspace test environment",
90 tags: { fixture: "finspace-environment" },
91 });
92 return { env };
93 }),
94 );
95
96 expect(env.environmentId).toBeDefined();
97 expect(env.environmentArn).toContain(":environment/");
98 expect(env.status).toBe("CREATED");
99 expect(env.tags.fixture).toBe("finspace-environment");
100
101 // Out-of-band verification via distilled.
102 const observed = yield* finspace.getEnvironment({
103 environmentId: env.environmentId,
104 });
105 expect(observed.environment?.status).toBe("CREATED");
106 expect(observed.environment?.name).toBe(env.name);
107
108 // Update the description in place (no replacement).
109 const { env: updated } = yield* stack.deploy(
110 Effect.gen(function* () {
111 const env = yield* Environment("Analytics", {
112 description: "alchemy finspace test environment (updated)",
113 tags: { fixture: "finspace-environment" },
114 });
115 return { env };
116 }),
117 );
118 expect(updated.environmentId).toBe(env.environmentId);
119
120 // Destroy immediately — environments bill while they exist — and
121 // verify deletion was initiated out-of-band.
122 yield* stack.destroy();
123 yield* assertEnvironmentDeleting(env.environmentId);
124 }),
125 // environment create (~20 min) + update + delete, one test.
126 {
127 tags: ["provider:aws", "provider:aws:finspace", "live"],
128 timeout: 3_000_000,
129 },
130);