1import { GcpEnvironment } from "@/GCP/Environment";
2import * as GCP from "@/GCP";
3import * as Test from "@/Test/Alchemy";
4import * as alloydb from "@distilled.cloud/gcp/alloydb_v1";
5import { expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7import { MinimumLogLevel } from "effect/References";
8import * as Schedule from "effect/Schedule";
9
10const { test } = Test.make({ providers: GCP.providers() });
11
12const logLevel = Effect.provideService(
13 MinimumLogLevel,
14 process.env.DEBUG ? "Debug" : "Info",
15);
16
17// AlloyDB clusters and instances take well over 5 minutes to provision.
18const runLifecycle = !!process.env.GCP_TEST_SLOW && !process.env.FAST;
19
20const waitUntilGone = (name: string) =>
21 alloydb.getProjectsLocationsClusters({ name }).pipe(
22 Effect.as("found" as const),
23 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
24 Effect.repeat({
25 schedule: Schedule.spaced("2 seconds"),
26 until: (status) => status === "gone",
27 times: 10,
28 }),
29 );
30
31test.provider(
32 "getProjectsLocationsClusters on a missing cluster fails with a typed tag",
33 (stack) =>
34 Effect.gen(function* () {
35 const { project } = yield* GcpEnvironment.current;
36
37 yield* stack.destroy();
38
39 const error = yield* Effect.flip(
40 alloydb.getProjectsLocationsClusters({
41 name: `projects/${project}/locations/us-central1/clusters/alchemy-alloydb-missing`,
42 }),
43 );
44 expect(error._tag).toEqual("NotFound");
45
46 const page = yield* alloydb.listProjectsLocationsClusters({
47 parent: `projects/${project}/locations/-`,
48 pageSize: 10,
49 });
50 expect((page.clusters ?? []).map((item) => item.name)).not.toContain(
51 `projects/${project}/locations/us-central1/clusters/alchemy-alloydb-missing`,
52 );
53
54 yield* stack.destroy();
55 }).pipe(logLevel),
56 { tags: ["provider:gcp", "provider:gcp:alloydb", "live"], timeout: 90_000 },
57);
58
59test.provider.skipIf(!runLifecycle)(
60 "create, update, and delete an alloydb cluster",
61 (stack) =>
62 Effect.gen(function* () {
63 yield* stack.destroy();
64
65 const created = yield* stack.deploy(
66 Effect.gen(function* () {
67 return yield* GCP.AlloyDB.Cluster("AppDb", {
68 location: "us-central1",
69 pscConfig: { pscEnabled: true },
70 displayName: "alchemy-test-cluster",
71 labels: { env: "test" },
72 initialUser: { user: "postgres", password: "AlchemyTest1" },
73 automatedBackupPolicy: { enabled: false },
74 continuousBackupConfig: { enabled: false },
75 });
76 }),
77 );
78
79 expect(created.name).toContain("/clusters/");
80 expect(created.clusterId).toEqual(expect.any(String));
81 expect(created.location).toEqual("us-central1");
82 expect(created.displayName).toEqual("alchemy-test-cluster");
83 expect(created.labels).toMatchObject({ env: "test" });
84 expect(created.pscConfig?.pscEnabled).toEqual(true);
85 expect(["READY", "EMPTY"]).toContain(created.state);
86
87 const fetched = yield* alloydb.getProjectsLocationsClusters({
88 name: created.name,
89 });
90 expect(fetched.name).toEqual(created.name);
91 expect(fetched.labels?.env).toEqual("test");
92 // GCP omits displayName from cluster reads, so only the resource
93 // attributes (asserted above) carry it.
94 expect(fetched.pscConfig?.pscEnabled).toEqual(true);
95
96 const updated = yield* stack.deploy(
97 Effect.gen(function* () {
98 return yield* GCP.AlloyDB.Cluster("AppDb", {
99 clusterId: created.clusterId,
100 location: "us-central1",
101 pscConfig: { pscEnabled: true },
102 displayName: "alchemy-prod-cluster",
103 labels: { env: "prod", role: "db" },
104 automatedBackupPolicy: { enabled: false },
105 continuousBackupConfig: { enabled: false },
106 });
107 }),
108 );
109
110 expect(updated.name).toEqual(created.name);
111 expect(updated.displayName).toEqual("alchemy-prod-cluster");
112 expect(updated.labels).toMatchObject({ env: "prod", role: "db" });
113
114 const refetched = yield* alloydb.getProjectsLocationsClusters({
115 name: created.name,
116 });
117 // GCP omits displayName from cluster reads, so only the resource
118 // attributes (asserted above) carry it.
119 expect(refetched.labels?.env).toEqual("prod");
120 expect(refetched.labels?.role).toEqual("db");
121
122 yield* stack.destroy();
123
124 const gone = yield* waitUntilGone(created.name);
125 expect(gone).toEqual("gone");
126 }).pipe(logLevel),
127 {
128 tags: ["provider:gcp", "provider:gcp:alloydb", "live"],
129 timeout: 3_600_000,
130 retry: 0,
131 },
132);