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 = (healthSource: string) =>
20 GcpEnvironment.current.pipe(
21 Effect.flatMap(({ project }) =>
22 compute
23 .getRegionHealthSources({
24 project,
25 region,
26 healthSource,
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
40const resourceTail = (value: string | undefined): string => {
41 if (value === undefined || value.length === 0) return "";
42 const parts = value.split("/").filter((part) => part.length > 0);
43 return parts[parts.length - 1] ?? "";
44};
45
46test.provider(
47 "getRegionHealthSources on a missing source fails with NotFound",
48 (stack) =>
49 Effect.gen(function* () {
50 const { project } = yield* GcpEnvironment.current;
51 yield* stack.destroy();
52
53 const error = yield* Effect.flip(
54 compute.getRegionHealthSources({
55 project,
56 region,
57 healthSource: "alchemy-missing-hs",
58 }),
59 );
60 expect(error._tag).toBe("NotFound");
61
62 yield* stack.destroy();
63 }).pipe(logLevel),
64 { tags: ["provider:gcp", "provider:gcp:compute", "live"], timeout: 90_000 },
65);
66
67test.provider.skipIf(!!process.env.FAST)(
68 "create, update, and delete a regional health source",
69 (stack) =>
70 Effect.gen(function* () {
71 yield* stack.destroy();
72
73 const created = yield* stack.deploy(
74 Effect.gen(function* () {
75 const policy = yield* GCP.Compute.RegionHealthAggregationPolicy(
76 "Agg",
77 {
78 region,
79 description: "backend rollup",
80 },
81 );
82 const backend = yield* GCP.Compute.RegionBackendService("Web", {
83 region,
84 protocol: "TCP",
85 loadBalancingScheme: "INTERNAL_MANAGED",
86 description: "health source backend",
87 });
88 const source = yield* GCP.Compute.RegionHealthSource("Src", {
89 region,
90 sources: [backend.selfLink.as<string>()],
91 healthAggregationPolicy: policy.selfLink.as<string>(),
92 description: "internal backends",
93 });
94 return { policy, backend, source };
95 }),
96 );
97
98 expect(created.source.sourceName).toEqual(expect.any(String));
99 expect(created.source.region).toEqual(region);
100 expect(created.source.sourceType).toEqual("BACKEND_SERVICE");
101 expect(created.source.description).toEqual("internal backends");
102 expect(created.source.sources.length).toBeGreaterThan(0);
103
104 const fetched = yield* compute.getRegionHealthSources({
105 project: created.source.project,
106 region,
107 healthSource: created.source.sourceName,
108 });
109 expect(fetched.name).toEqual(created.source.sourceName);
110 expect(fetched.sourceType).toEqual("BACKEND_SERVICE");
111 expect(fetched.description).toContain("[alchemy ");
112 expect(fetched.description).toContain("internal backends");
113 expect(resourceTail(fetched.healthAggregationPolicy)).toEqual(
114 created.policy.policyName,
115 );
116
117 const updated = yield* stack.deploy(
118 Effect.gen(function* () {
119 const policy = yield* GCP.Compute.RegionHealthAggregationPolicy(
120 "Agg",
121 {
122 policyName: created.policy.policyName,
123 region,
124 description: "backend rollup",
125 },
126 );
127 const backend = yield* GCP.Compute.RegionBackendService("Web", {
128 name: created.backend.name,
129 region,
130 protocol: "TCP",
131 loadBalancingScheme: "INTERNAL_MANAGED",
132 description: "health source backend",
133 });
134 return yield* GCP.Compute.RegionHealthSource("Src", {
135 sourceName: created.source.sourceName,
136 region,
137 sources: [backend.selfLink.as<string>()],
138 healthAggregationPolicy: policy.selfLink.as<string>(),
139 description: "updated backends",
140 });
141 }),
142 );
143
144 expect(updated.sourceName).toEqual(created.source.sourceName);
145 expect(updated.description).toEqual("updated backends");
146
147 const refetched = yield* compute.getRegionHealthSources({
148 project: updated.project,
149 region,
150 healthSource: updated.sourceName,
151 });
152 expect(refetched.description).toContain("updated backends");
153
154 yield* stack.destroy();
155
156 const gone = yield* waitUntilGone(created.source.sourceName);
157 expect(gone).toEqual("gone");
158 }).pipe(logLevel),
159 { tags: ["provider:gcp", "provider:gcp:compute", "live"], timeout: 120_000 },
160);