1import * as GCP from "@/GCP";
2import type { StackServices } from "@/Stack";
3import * as Test from "@/Test/Alchemy";
4import * as sqladmin from "@distilled.cloud/gcp/sqladmin_v1";
5import { expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7import * as Layer from "effect/Layer";
8import { MinimumLogLevel } from "effect/References";
9import * as Schedule from "effect/Schedule";
10import { GcpEnvironment } from "@/GCP/Environment";
11
12const { test } = Test.make({
13 providers: GCP.providers() as Layer.Layer<
14 GCP.ProviderRequirements,
15 never,
16 StackServices
17 >,
18});
19
20const logLevel = Effect.provideService(
21 MinimumLogLevel,
22 process.env.DEBUG ? "Debug" : "Info",
23);
24
25// Cloud SQL instances take well over 5 minutes to provision.
26const runLifecycle = !!process.env.GCP_TEST_SLOW && !process.env.FAST;
27
28const waitUntilGone = (project: string, instance: string) =>
29 sqladmin.getInstances({ project, instance }).pipe(
30 Effect.as("found" as const),
31 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
32 Effect.repeat({
33 schedule: Schedule.spaced("2 seconds"),
34 until: (status) => status === "gone",
35 times: 10,
36 }),
37 );
38
39test.provider(
40 "lists sql instances and treats a missing instance as NotFound",
41 (stack) =>
42 Effect.gen(function* () {
43 yield* stack.destroy();
44
45 const { project } = yield* GcpEnvironment.current;
46 const page = yield* sqladmin.listInstances({
47 project,
48 maxResults: 10,
49 });
50 expect((page.items ?? []).map((instance) => instance.name)).not.toContain(
51 "alchemy-sql-instance-does-not-exist",
52 );
53
54 const error = yield* Effect.flip(
55 sqladmin.getInstances({
56 project,
57 instance: "alchemy-sql-instance-does-not-exist",
58 }),
59 );
60 expect(error._tag).toBe("NotFound");
61
62 yield* stack.destroy();
63 }).pipe(logLevel),
64 { tags: ["provider:gcp", "provider:gcp:sql", "live"], timeout: 90_000 },
65);
66
67test.provider.skipIf(!runLifecycle)(
68 "create, update, and delete a sql instance",
69 (stack) =>
70 Effect.gen(function* () {
71 yield* stack.destroy();
72
73 const created = yield* stack.deploy(
74 Effect.gen(function* () {
75 return yield* GCP.SQL.Instance("AppDb", {
76 region: "us-central1",
77 databaseVersion: "MYSQL_8_0",
78 tier: "db-f1-micro",
79 backupEnabled: false,
80 deletionProtectionEnabled: false,
81 labels: { env: "test" },
82 });
83 }),
84 );
85
86 expect(created.instanceName).toEqual(expect.any(String));
87 expect(created.region).toEqual("us-central1");
88 expect(created.databaseVersion).toEqual("MYSQL_8_0");
89 expect(created.tier).toEqual("db-f1-micro");
90 expect(created.labels).toMatchObject({ env: "test" });
91 expect(created.deletionProtectionEnabled).toEqual(false);
92 expect(created.state).toEqual("RUNNABLE");
93
94 const fetched = yield* sqladmin.getInstances({
95 project: created.project,
96 instance: created.instanceName,
97 });
98 expect(fetched.name).toEqual(created.instanceName);
99 expect(fetched.settings?.userLabels?.env).toEqual("test");
100 expect(fetched.settings?.tier).toEqual("db-f1-micro");
101
102 const updated = yield* stack.deploy(
103 Effect.gen(function* () {
104 return yield* GCP.SQL.Instance("AppDb", {
105 instanceName: created.instanceName,
106 region: "us-central1",
107 databaseVersion: "MYSQL_8_0",
108 tier: "db-f1-micro",
109 backupEnabled: false,
110 deletionProtectionEnabled: false,
111 labels: { env: "prod", role: "db" },
112 });
113 }),
114 );
115
116 expect(updated.instanceName).toEqual(created.instanceName);
117 expect(updated.labels).toMatchObject({ env: "prod", role: "db" });
118
119 const refetched = yield* sqladmin.getInstances({
120 project: created.project,
121 instance: created.instanceName,
122 });
123 expect(refetched.settings?.userLabels?.env).toEqual("prod");
124 expect(refetched.settings?.userLabels?.role).toEqual("db");
125
126 yield* stack.destroy();
127
128 const gone = yield* waitUntilGone(created.project, created.instanceName);
129 expect(gone).toEqual("gone");
130 }).pipe(logLevel),
131 // Create 5–15 minutes, label patch, delete a few minutes.
132 {
133 tags: ["provider:gcp", "provider:gcp:sql", "live"],
134 timeout: 2_400_000,
135 retry: 0,
136 },
137);