1import * as AWS from "@/AWS";
2import { Database, Table } from "@/AWS/Timestream";
3import {
4 withQueryEndpoint,
5 withWriteEndpoint,
6} from "@/AWS/Timestream/internal";
7import * as Test from "@/Test/Alchemy";
8import * as TSQ from "@distilled.cloud/aws/timestream-query";
9import * as TSW from "@distilled.cloud/aws/timestream-write";
10import { describe, expect } from "alchemy-test";
11import * as Effect from "effect/Effect";
12
13const { test } = Test.make({ providers: AWS.providers() });
14
15// Amazon Timestream for LiveAnalytics is closed to new AWS customers; accounts
16// that were not already onboarded receive `TimestreamNotOnboarded` (a
17// specialized AccessDenied) on every operation, including the endpoint-
18// discovery call every request must make first. The ungated probes assert that
19// gate is surfaced as a typed error; the full lifecycle is gated behind
20// AWS_TEST_TIMESTREAM=1 so an onboarded account can run it unchanged.
21describe(
22 "AWS.Timestream.Database",
23 { tags: ["provider:aws", "provider:aws:timestream", "live"] },
24 () => {
25 test.provider(
26 "write DescribeEndpoints reports typed TimestreamNotOnboarded",
27 (_stack) =>
28 Effect.gen(function* () {
29 const error = yield* TSW.describeEndpoints({}).pipe(Effect.flip);
30 expect(error._tag).toBe("TimestreamNotOnboarded");
31 }),
32 { timeout: 60_000 },
33 );
34
35 test.provider(
36 "query DescribeEndpoints reports typed TimestreamNotOnboarded",
37 (_stack) =>
38 Effect.gen(function* () {
39 const error = yield* TSQ.describeEndpoints({}).pipe(Effect.flip);
40 expect(error._tag).toBe("TimestreamNotOnboarded");
41 }),
42 { timeout: 60_000 },
43 );
44
45 test.provider.skipIf(!process.env.AWS_TEST_TIMESTREAM)(
46 "create database + table, write + query, then delete",
47 (stack) =>
48 Effect.gen(function* () {
49 const { database, table } = yield* stack.deploy(
50 Effect.gen(function* () {
51 const database = yield* Database("Metrics");
52 const table = yield* Table("Cpu", {
53 databaseName: database.databaseName,
54 retentionProperties: {
55 memoryStoreRetention: "6 hours",
56 magneticStoreRetention: "30 days",
57 },
58 });
59 return { database, table };
60 }),
61 );
62
63 expect(database.databaseArn).toBeDefined();
64 expect(table.tableArn).toBeDefined();
65 expect(table.tableStatus).toBe("ACTIVE");
66
67 // Out-of-band verification via distilled through the discovered
68 // endpoint.
69 const described = yield* withWriteEndpoint(
70 TSW.describeTable({
71 DatabaseName: database.databaseName,
72 TableName: table.tableName,
73 }),
74 );
75 expect(described.Table?.TableStatus).toBe("ACTIVE");
76
77 // Write a point and query it back.
78 yield* withWriteEndpoint(
79 TSW.writeRecords({
80 DatabaseName: database.databaseName,
81 TableName: table.tableName,
82 Records: [
83 {
84 Dimensions: [{ Name: "host", Value: "web-1" }],
85 MeasureName: "cpu",
86 MeasureValue: "42.0",
87 MeasureValueType: "DOUBLE",
88 Time: `${Date.now()}`,
89 TimeUnit: "MILLISECONDS",
90 },
91 ],
92 }),
93 );
94
95 const queried = yield* withQueryEndpoint(
96 TSQ.query({
97 QueryString: `SELECT COUNT(*) FROM "${database.databaseName}"."${table.tableName}"`,
98 }),
99 );
100 expect(queried.Rows.length).toBeGreaterThan(0);
101
102 yield* stack.destroy();
103
104 const gone = yield* withWriteEndpoint(
105 TSW.describeDatabase({ DatabaseName: database.databaseName }),
106 ).pipe(
107 Effect.map(() => false),
108 Effect.catchTag("ResourceNotFoundException", () =>
109 Effect.succeed(true),
110 ),
111 );
112 expect(gone).toBe(true);
113 }),
114 { timeout: 300_000 },
115 );
116 },
117);