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
17const region = "us-central1";
18
19const waitUntilGone = (notificationEndpoint: string) =>
20 GcpEnvironment.current.pipe(
21 Effect.flatMap(({ project }) =>
22 compute
23 .getRegionNotificationEndpoints({
24 project,
25 region,
26 notificationEndpoint,
27 })
28 .pipe(
29 Effect.as("found" as const),
30 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
31 Effect.repeat({
32 schedule: Schedule.spaced("1 second"),
33 until: (status) => status === "gone",
34 times: 10,
35 }),
36 ),
37 ),
38 );
39
40test.provider(
41 "getRegionNotificationEndpoints on a missing endpoint fails with NotFound",
42 (stack) =>
43 Effect.gen(function* () {
44 const { project } = yield* GcpEnvironment.current;
45 yield* stack.destroy();
46
47 const error = yield* Effect.flip(
48 compute.getRegionNotificationEndpoints({
49 project,
50 region,
51 notificationEndpoint: "alchemy-missing-ne",
52 }),
53 );
54 expect(error._tag).toBe("NotFound");
55
56 yield* stack.destroy();
57 }).pipe(logLevel),
58 { tags: ["provider:gcp", "provider:gcp:compute", "live"], timeout: 90_000 },
59);
60
61// Health Check as a Service is allowlisted; without it insert fails with
62// `BadRequest: Invalid resource usage: 'HealthCheck as a Service feature is
63// not available for this project.'`. Set GCP_TEST_HCAAS=1 on an allowlisted
64// project.
65test.provider.skipIf(!!process.env.FAST || !process.env.GCP_TEST_HCAAS)(
66 "create, replace, and delete a regional notification endpoint",
67 (stack) =>
68 Effect.gen(function* () {
69 yield* stack.destroy();
70
71 const created = yield* stack.deploy(
72 Effect.gen(function* () {
73 return yield* GCP.Compute.RegionNotificationEndpoint("Health", {
74 region,
75 description: "regional health callbacks",
76 grpcSettings: {
77 endpoint: "health.example.com:443",
78 retryDurationSec: 30,
79 },
80 });
81 }),
82 );
83
84 expect(created.notificationEndpointName).toEqual(expect.any(String));
85 expect(created.region).toEqual(region);
86 expect(created.description).toEqual("regional health callbacks");
87 expect(created.grpcEndpoint).toEqual("health.example.com:443");
88 expect(created.retryDurationSec).toEqual(30);
89
90 const fetched = yield* compute.getRegionNotificationEndpoints({
91 project: created.project,
92 region,
93 notificationEndpoint: created.notificationEndpointName,
94 });
95 expect(fetched.name).toEqual(created.notificationEndpointName);
96 expect(fetched.grpcSettings?.endpoint).toEqual("health.example.com:443");
97 expect(fetched.description).toContain("[alchemy ");
98 expect(fetched.description).toContain("regional health callbacks");
99
100 const updated = yield* stack.deploy(
101 Effect.gen(function* () {
102 return yield* GCP.Compute.RegionNotificationEndpoint("Health", {
103 notificationEndpointName: created.notificationEndpointName,
104 region,
105 description: "updated health callbacks",
106 grpcSettings: {
107 endpoint: "health.example.com:443",
108 retryDurationSec: 60,
109 },
110 });
111 }),
112 );
113
114 expect(updated.notificationEndpointName).toEqual(
115 created.notificationEndpointName,
116 );
117 expect(updated.description).toEqual("updated health callbacks");
118 expect(updated.retryDurationSec).toEqual(60);
119
120 const refetched = yield* compute.getRegionNotificationEndpoints({
121 project: updated.project,
122 region,
123 notificationEndpoint: updated.notificationEndpointName,
124 });
125 expect(refetched.description).toContain("updated health callbacks");
126 expect(refetched.grpcSettings?.retryDurationSec).toEqual(60);
127
128 yield* stack.destroy();
129
130 const gone = yield* waitUntilGone(created.notificationEndpointName);
131 expect(gone).toEqual("gone");
132 }).pipe(logLevel),
133 { tags: ["provider:gcp", "provider:gcp:compute", "live"], timeout: 90_000 },
134);