1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as compute from "@distilled.cloud/gcp/compute_v1";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import { MinimumLogLevel } from "effect/References";
7import * as Schedule from "effect/Schedule";
8import { GcpEnvironment } from "@/GCP/Environment";
9
10const { test } = Test.make({ providers: GCP.providers() });
11
12const logLevel = Effect.provideService(
13 MinimumLogLevel,
14 process.env.DEBUG ? "Debug" : "Info",
15);
16
17// Health Check as a Service is allowlisted; without it insert fails with
18// `BadRequest: Invalid resource usage: 'HealthCheck as a Service feature is
19// not available for this project.'`. Set GCP_TEST_HCAAS=1 on an allowlisted
20// project.
21const runLifecycle = !!process.env.GCP_TEST_HCAAS && !process.env.FAST;
22
23const region = "us-central1";
24
25const waitUntilGone = (
26 project: string,
27 regionName: string,
28 healthCheckService: string,
29) =>
30 compute
31 .getRegionHealthCheckServices({
32 project,
33 region: regionName,
34 healthCheckService,
35 })
36 .pipe(
37 Effect.as("found" as const),
38 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
39 Effect.repeat({
40 schedule: Schedule.spaced("1 second"),
41 until: (status) => status === "gone",
42 times: 10,
43 }),
44 );
45
46test.provider(
47 "insertRegionHealthCheckServices without the feature fails with HealthCheckServiceNotAvailable",
48 () =>
49 Effect.gen(function* () {
50 const { project } = yield* GcpEnvironment.current;
51 const error = yield* Effect.flip(
52 compute.insertRegionHealthCheckServices({
53 project,
54 region,
55 body: {
56 name: "alchemy-hcss-probe",
57 description: "alchemy entitlement probe",
58 healthChecks: [
59 `projects/${project}/regions/${region}/healthChecks/does-not-exist`,
60 ],
61 },
62 }),
63 );
64 expect(error._tag).toEqual("HealthCheckServiceNotAvailable");
65 }).pipe(logLevel),
66 { tags: ["provider:gcp", "provider:gcp:compute", "live"], timeout: 60_000 },
67);
68
69test.provider.skipIf(!runLifecycle)(
70 "create, update, and delete a region health check service",
71 (stack) =>
72 Effect.gen(function* () {
73 yield* stack.destroy();
74
75 const created = yield* stack.deploy(
76 Effect.gen(function* () {
77 const check = yield* GCP.Compute.RegionHealthCheck("Api", {
78 region,
79 httpHealthCheck: {
80 port: 80,
81 portSpecification: "USE_FIXED_PORT",
82 requestPath: "/health",
83 },
84 });
85 return yield* GCP.Compute.RegionHealthCheckService("Hcss", {
86 region,
87 healthChecks: [check.selfLink.as<string>()],
88 description: "endpoint health",
89 });
90 }),
91 );
92
93 expect(created.serviceName).toEqual(expect.any(String));
94 expect(created.region).toEqual(region);
95 expect(created.description).toEqual("endpoint health");
96 expect(created.healthChecks.length).toBeGreaterThan(0);
97 expect(created.healthStatusAggregationPolicy).toEqual("NO_AGGREGATION");
98
99 const fetched = yield* compute.getRegionHealthCheckServices({
100 project: created.project,
101 region,
102 healthCheckService: created.serviceName,
103 });
104 expect(fetched.name).toEqual(created.serviceName);
105 expect(fetched.description).toContain("[alchemy ");
106 expect(fetched.description).toContain("endpoint health");
107 expect(fetched.healthChecks?.length).toBeGreaterThan(0);
108
109 const updated = yield* stack.deploy(
110 Effect.gen(function* () {
111 const check = yield* GCP.Compute.RegionHealthCheck("Api", {
112 region,
113 httpHealthCheck: {
114 port: 80,
115 portSpecification: "USE_FIXED_PORT",
116 requestPath: "/health",
117 },
118 });
119 return yield* GCP.Compute.RegionHealthCheckService("Hcss", {
120 serviceName: created.serviceName,
121 region,
122 healthChecks: [check.selfLink.as<string>()],
123 description: "updated health",
124 });
125 }),
126 );
127
128 expect(updated.serviceName).toEqual(created.serviceName);
129 expect(updated.description).toEqual("updated health");
130
131 const refetched = yield* compute.getRegionHealthCheckServices({
132 project: updated.project,
133 region,
134 healthCheckService: updated.serviceName,
135 });
136 expect(refetched.description).toContain("updated health");
137
138 yield* stack.destroy();
139
140 const gone = yield* waitUntilGone(
141 created.project,
142 region,
143 created.serviceName,
144 );
145 expect(gone).toEqual("gone");
146 }).pipe(logLevel),
147 { tags: ["provider:gcp", "provider:gcp:compute", "live"], timeout: 120_000 },
148);