1import * as AWS from "@/AWS";
2import * as IAM from "@/AWS/IAM";
3import * as S3 from "@/AWS/S3";
4import * as SNS from "@/AWS/SNS";
5import { Database, ScheduledQuery, Table } from "@/AWS/Timestream";
6import { withQueryEndpoint } from "@/AWS/Timestream/internal";
7import * as Output from "@/Output";
8import * as Test from "@/Test/Alchemy";
9import * as TSQ from "@distilled.cloud/aws/timestream-query";
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; the
16// testing account receives the typed `TimestreamNotOnboarded` gate on every
17// operation, including the endpoint-discovery call every scheduled-query
18// request must make first. The ungated probe asserts the gate surfaces as a
19// typed error through the discovery wrapper the provider uses; the full
20// lifecycle is gated behind AWS_TEST_TIMESTREAM=1 so an onboarded account can
21// run it unchanged.
22describe(
23 "AWS.Timestream.ScheduledQuery",
24 { tags: ["provider:aws", "provider:aws:timestream", "live"] },
25 () => {
26 test.provider(
27 "listScheduledQueries reports typed TimestreamNotOnboarded via endpoint discovery",
28 (_stack) =>
29 Effect.gen(function* () {
30 const error = yield* withQueryEndpoint(
31 TSQ.listScheduledQueries({}),
32 ).pipe(Effect.flip);
33 expect(error._tag).toBe("TimestreamNotOnboarded");
34 }),
35 { timeout: 60_000 },
36 );
37
38 test.provider.skipIf(!process.env.AWS_TEST_TIMESTREAM)(
39 "create, pause, and delete a scheduled query",
40 (stack) =>
41 Effect.gen(function* () {
42 const infra = Effect.gen(function* () {
43 const database = yield* Database("SqMetrics");
44 const table = yield* Table("SqCpu", {
45 databaseName: database.databaseName,
46 });
47 const topic = yield* SNS.Topic("SqNotifications");
48 const bucket = yield* S3.Bucket("SqErrorReports", {
49 forceDestroy: true,
50 });
51 const role = yield* IAM.Role("SqExecutionRole", {
52 assumeRolePolicyDocument: {
53 Version: "2012-10-17",
54 Statement: [
55 {
56 Effect: "Allow",
57 Principal: { Service: "timestream.amazonaws.com" },
58 Action: ["sts:AssumeRole"],
59 },
60 ],
61 },
62 inlinePolicies: {
63 scheduledQuery: {
64 Version: "2012-10-17",
65 Statement: [
66 {
67 Effect: "Allow",
68 Action: [
69 "timestream:Select",
70 "timestream:SelectValues",
71 "timestream:WriteRecords",
72 "timestream:DescribeEndpoints",
73 ],
74 Resource: ["*"],
75 },
76 {
77 Effect: "Allow",
78 Action: ["sns:Publish"],
79 Resource: [topic.topicArn],
80 },
81 {
82 Effect: "Allow",
83 Action: ["s3:PutObject", "s3:GetBucketAcl"],
84 Resource: [
85 bucket.bucketArn,
86 Output.interpolate`${bucket.bucketArn}/*`,
87 ],
88 },
89 ],
90 },
91 },
92 });
93 const scheduledQuery = yield* ScheduledQuery("HourlyCount", {
94 queryString: Output.interpolate`SELECT COUNT(*) FROM "${database.databaseName}"."${table.tableName}"`,
95 scheduleExpression: "rate(1 hour)",
96 notificationTopicArn: topic.topicArn,
97 executionRoleArn: role.roleArn,
98 errorReportS3: { bucketName: bucket.bucketName },
99 tags: { Environment: "test" },
100 });
101 return { scheduledQuery };
102 });
103
104 const { scheduledQuery } = yield* stack.deploy(infra);
105 expect(scheduledQuery.scheduledQueryArn).toBeTruthy();
106 expect(scheduledQuery.state).toBe("ENABLED");
107
108 // Out-of-band verification via distilled through the discovered
109 // endpoint.
110 const described = yield* withQueryEndpoint(
111 TSQ.describeScheduledQuery({
112 ScheduledQueryArn: scheduledQuery.scheduledQueryArn,
113 }),
114 );
115 expect(described.ScheduledQuery.State).toBe("ENABLED");
116
117 yield* stack.destroy();
118
119 const gone = yield* withQueryEndpoint(
120 TSQ.describeScheduledQuery({
121 ScheduledQueryArn: scheduledQuery.scheduledQueryArn,
122 }),
123 ).pipe(
124 Effect.map(() => false),
125 Effect.catchTag("ResourceNotFoundException", () =>
126 Effect.succeed(true),
127 ),
128 );
129 expect(gone).toBe(true);
130 }),
131 {
132 tags: ["provider:aws:iam", "provider:aws:s3", "provider:aws:sns"],
133 timeout: 600_000,
134 },
135 );
136 },
137);