1import * as AWS from "@/AWS";
2import { DbInstance } from "@/AWS/Timestream";
3import * as Test from "@/Test/Alchemy";
4import * as influxdb from "@distilled.cloud/aws/timestream-influxdb";
5import { describe, 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
12// timestream-influxdb IS accessible (unlike Timestream LiveAnalytics), but
13// provisioning a DB instance takes ~15–20 minutes and is EC2-backed/costly.
14// The ungated probe asserts the distilled wiring produces a typed error; the
15// full lifecycle is gated behind AWS_TEST_SLOW=1.
16describe(
17 "AWS.Timestream.DbInstance",
18 { tags: ["provider:aws", "provider:aws:timestream", "live"] },
19 () => {
20 test.provider(
21 "getDbInstance for a bad identifier yields a typed ValidationException",
22 (_stack) =>
23 Effect.gen(function* () {
24 // A malformed identifier is rejected up-front with a typed
25 // ValidationException (a well-formed but absent id would instead yield
26 // ResourceNotFoundException); either way the distilled wiring surfaces
27 // a typed error rather than an untyped catch-all.
28 const error = yield* influxdb
29 .getDbInstance({ identifier: "alchemy-timestream-does-not-exist" })
30 .pipe(Effect.flip);
31 expect([
32 "ValidationException",
33 "ResourceNotFoundException",
34 ]).toContain(error._tag);
35 }),
36 { timeout: 60_000 },
37 );
38
39 test.provider.skipIf(!process.env.AWS_TEST_SLOW)(
40 "create, wait, and delete an InfluxDB instance",
41 (stack) =>
42 Effect.gen(function* () {
43 const subnetIds = (process.env.AWS_TEST_SUBNET_IDS ?? "")
44 .split(",")
45 .map((s) => s.trim())
46 .filter(Boolean);
47 const securityGroupIds = (
48 process.env.AWS_TEST_SECURITY_GROUP_IDS ?? ""
49 )
50 .split(",")
51 .map((s) => s.trim())
52 .filter(Boolean);
53 expect(subnetIds.length).toBeGreaterThan(0);
54 expect(securityGroupIds.length).toBeGreaterThan(0);
55
56 const instance = yield* stack.deploy(
57 Effect.gen(function* () {
58 return yield* DbInstance("Influx", {
59 name: "alchemy-influx-test",
60 dbInstanceType: "db.influx.medium",
61 allocatedStorage: 20,
62 vpcSubnetIds: subnetIds,
63 vpcSecurityGroupIds: securityGroupIds,
64 password: Redacted.make("alchemy-super-secret-pw-1"),
65 tags: { Environment: "test" },
66 });
67 }),
68 );
69
70 expect(instance.id).toBeDefined();
71 expect(instance.arn).toBeDefined();
72 expect(instance.status).toBe("AVAILABLE");
73
74 const described = yield* influxdb.getDbInstance({
75 identifier: instance.id,
76 });
77 expect(described.name).toBe("alchemy-influx-test");
78
79 yield* stack.destroy();
80
81 yield* Effect.gen(function* () {
82 const gone = yield* influxdb
83 .getDbInstance({ identifier: instance.id })
84 .pipe(
85 Effect.map(() => false),
86 Effect.catchTag("ResourceNotFoundException", () =>
87 Effect.succeed(true),
88 ),
89 );
90 if (!gone)
91 return yield* Effect.fail({ _tag: "StillExists" as const });
92 }).pipe(
93 Effect.retry({
94 while: (e: { _tag: string }) => e._tag === "StillExists",
95 schedule: Schedule.max([
96 Schedule.spaced("20 seconds"),
97 Schedule.recurs(90),
98 ]),
99 }),
100 );
101 }),
102 { timeout: 2_400_000 },
103 );
104 },
105);