1import * as AWS from "@/AWS";
2import * as Core from "@/Test/Core";
3import * as Test from "@/Test/Alchemy";
4import { describe, expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import * as Schedule from "effect/Schedule";
7import * as HttpClient from "effect/http/HttpClient";
8import RedshiftQueryFunctionLive, {
9 RedshiftQueryFunction,
10} from "./fixtures/query-handler";
11
12const testOptions = { providers: AWS.providers() };
13const { test, beforeAll, afterAll } = Test.make(testOptions);
14const sharedStack = Core.scratchStack(testOptions, "RedshiftDataBindings");
15
16let baseUrl: string;
17
18// The whole flagship suite deploys a Redshift Serverless namespace + workgroup
19// (a real-money RPU floor while it exists) plus a Lambda, so it is gated behind
20// AWS_TEST_SLOW=1 and destroys everything in afterAll.
21describe.skipIf(!process.env.AWS_TEST_SLOW)(
22 "RedshiftData Bindings",
23 {
24 tags: [
25 "provider:aws",
26 "provider:aws:lambda",
27 "provider:aws:redshiftdata",
28 "provider:aws:redshiftserverless",
29 "live",
30 ],
31 },
32 () => {
33 beforeAll(
34 Effect.gen(function* () {
35 yield* Effect.logInfo(
36 "RedshiftData test setup: destroying previous run",
37 );
38 yield* sharedStack.destroy();
39
40 yield* Effect.logInfo("RedshiftData test setup: deploying fixture");
41 const { functionUrl } = yield* sharedStack.deploy(
42 Effect.gen(function* () {
43 return yield* RedshiftQueryFunction;
44 }).pipe(Effect.provide(RedshiftQueryFunctionLive)),
45 );
46
47 expect(functionUrl).toBeTruthy();
48 baseUrl = functionUrl!.replace(/\/+$/, "");
49 yield* Effect.logInfo(
50 `RedshiftData test setup: function URL ready (${functionUrl})`,
51 );
52 }),
53 // namespace (~1 min) + workgroup create (~2-5 min) + Lambda deploy.
54 { timeout: 900_000 },
55 );
56
57 afterAll(sharedStack.destroy(), { timeout: 600_000 });
58
59 describe("Statements", () => {
60 test.provider(
61 "runs SELECT 1 against the serverless workgroup and returns records",
62 (_stack) =>
63 Effect.gen(function* () {
64 const response = yield* HttpClient.get(`${baseUrl}/query`).pipe(
65 Effect.flatMap((res) =>
66 res.status === 200
67 ? Effect.succeed(res)
68 : Effect.fail(new Error(`query returned ${res.status}`)),
69 ),
70 Effect.retry({
71 schedule: Schedule.max([
72 Schedule.exponential("1 second"),
73 Schedule.recurs(10),
74 ]),
75 }),
76 Effect.flatMap((res) => res.json),
77 );
78
79 const body = response as {
80 columns: (string | undefined)[];
81 records: { longValue?: number }[][];
82 totalNumRows: number;
83 };
84 // SELECT 1 AS n => one row, one column, longValue 1.
85 expect(body.records).toHaveLength(1);
86 expect(body.records[0][0]).toMatchObject({ longValue: 1 });
87 }),
88 { timeout: 240_000 },
89 );
90 });
91 },
92);